Emacs Configuration
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.
| Quote | Attribution |
|---|---|
| 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 miserable | James A. Garfield |
| The thing that lies at the foundation of positive change is service to a fellow human being | Lee Iacocca |
| Honesty and transparency make you vulnerable. Be honest and transparent anyway | Mother 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 virtualenvwrapperInstall 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
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))
