Skip to content

My life is dope and I have dope emacs configs

Notifications You must be signed in to change notification settings

arecker/emacs.d

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Emacs Configuration

Introduction

This is my Emacs configuration. Before you even open Emacs, clone this repo in your home directory like this:

git clone git@github.com:arecker/emacs.d.git ~/.emacs.d

So where is the config? The document you are reading is the config. Org Babel, a native feature of Emacs that can run code snippets embedded in rich text documents, spurred a fad in the Emacs community where users define their personal configs in README documents like this one.

In that spirit, I tried to contain my actual lisp code in this document, save for one function that you need in init.el to kick off the process.

(defun recker/load-config ()
  "Tangle configuration and load it."
  (let ((config (concat (file-name-as-directory user-emacs-directory) "README.org")))
    (if (file-exists-p config)
        (org-babel-load-file config)
      (warn (concat config " not found - not loading")))))

(recker/load-config)

From here on, all lisp code snippets with syntax highlighting are run on startup in the order they appear.

One more note before we get going. You are free to use this as a starting place for your own configuration. All of my custom functions begin with the recker/ prefix. To change it, just perform a string replace on (defun recker/ within this document. Beyond that, just update these global variables with your own info.

(setq user-full-name "Alex Recker")
(setq user-mail-address "alex@reckerfamily.com")
(setq recker/work-mail-address "arecker@zendesk.com")

Packages

First, we setup the Emacs package manager. This occurs at the top of the file so we can install packages as we need them.

The Emacs package manager does a nice job. With the help of John Wiegley’s use-package, we can simultaneously declare a package we need and configure the package after it installs and loads.

As far as package maintenance, I sometimes run M-x package-list-packages to bring up the packages screen. You can press U then x at this screen to pull down any updates that are ready, and it’s also a good idea to restart Emacs to make sure everything is still working.

(require 'package)

(defun recker/configure-packages ()
  "Set up the package manager."

  ;; set and load custom file first (this is gitignored)
  (setq custom-file (concat user-emacs-directory "custom.el"))
  (if (file-exists-p custom-file) (load custom-file)
    (warn "couldn't find custom file %s" custom-file))

  ;; set the repo URLs
  (setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/")
                           ("melpa" . "https://melpa.org/packages/")))

  ;; initialize the package manager
  (package-initialize)

  ;; install use-package
  (unless (package-installed-p 'use-package)
    (package-refresh-contents)
    (package-install 'use-package)))

(recker/configure-packages)

Application

If you’re using Emacs through a CLI, I’d recommend you close it out and download the real thing instead (if you’re running a mac, Emacs for Mac OS X is great no-frills package). Flipping between emacs and the terminal is still fine, though. Especially since with this config, running the command emacsclient will connect to the running Emacs process seamlessly. You can even export EDITOR=emacsclient in your environment so other tools can get on the same page.

(defun recker/configure-application ()

  ;; prompt when quitting (protects from fat fingers)
  (setq-default confirm-kill-emacs #'yes-or-no-p)

  ;; start the server
  (server-start))

(recker/configure-application)

Appearance

Default Emacs gets a lot of crap for its “ugly” UI. I find it cozy and nostalgic. In an effort to maintain that unmistakable aesthetic, I make just a few modest tweaks to the appearance.

(defun recker/configure-appearance ()
  "Configure the Emacs UI."

  ;; Suppress widgets
  (menu-bar-mode 0)
  (tool-bar-mode 0)
  (scroll-bar-mode 0)

  ;; Set font
  (when (string= system-type "darwin")
    (set-frame-font "Monaco 18" nil t))
  (when (string= system-type "gnu/linux")
    (set-frame-font "Monospace 13" nil t))

  ;; Full screen by default
  (unless (eq (frame-parameter (selected-frame) 'fullscreen) 'maximized)
    (toggle-frame-maximized))

  ;; Use this package to hide minor modes.  I like to know which major
  ;; mode I'm editing, but the minor mode list gets a little too
  ;; cluttered trying to list all the plugins I have running.
  (unless (bound-and-true-p rich-minority-mode) ;it breaks if it runs twice?
    (use-package rich-minority
      :ensure t
      :init (rich-minority-mode 't)
      :config (setq rm-blacklist ""))))

(recker/configure-appearance)

Text

Automatically clean-up whitespace on save. Trailing whitespace is annoying, and it shouldn’t be there in the first place. Also, don’t insert tabs unless the major mode really wants to (golang, for example, will do its own thing).

(add-hook 'before-save-hook 'whitespace-cleanup)
(setq-default indent-tabs-mode nil)

Bind the build in function replace-string to C-c r. By default, delete the selected text when you hit “backspace”. Also, use upcase-region without Emacs bothering you about some nuance that I’ve never bothered to read closely - the function works just fine for me.

(global-set-key (kbd "C-c r") 'replace-string)
(setq delete-selection-mode 't)
(put 'upcase-region 'disabled nil)

Bind C-c l to the sort lines function. Sorry if this breaks some other workflow I don’t yet know about, but for some reason I find myself alphabetizing strings often enough to like this here.

(global-set-key (kbd "C-c l") #'sort-lines)

Bind the handy expand-region tool to C-\=. This tool can highlight incrementally larger portions of text like quotes, parentheses, and function definitions.

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

Use yasnippet for managing snippets of text. To create a new snippet, run M-x yas-new-snippet. This will open a buffer where, following some simple syntax rules, you can create dynamic snippets for any editing mode in Emacs. These are saved within the snippets/ directory of your emacs configuration.

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

Employ spell checking. Just make sure the ispell tool is installed. You can keep your own list of exceptions in ~/.ispell_words and Emacs is smart enough to add to this when you ask for it.

(use-package flyspell
  :config (setq ispell-program-name (executable-find "ispell"))
  :init (add-hook 'text-mode-hook #'(lambda () (flyspell-mode 1))))

Movement

The buffer would have to be the most common form of transportation in the Emacs world. Suppressing the more boisterous default splash screen, I’ve made the *scratch* buffer my home. With these configs, I’ve made it so that this buffer can never be deleted. I wrote a good amount of custom code to print output from the infamous fortune command (or another command if you want) on every launch. It’s also a great place to quickly test lisp expressions or paste random text.

;; don't show the splash screen
(setq inhibit-splash-screen 't)

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

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

;; display the output of "fortune" as the scratch message
(setq recker/scratch-message-command "fortune --wrap 72 --comment ';; '")

(defun recker/scratch-message ()
  "Return a scratch message from fortune-blog."
  (concat "\n"
          (recker/scratch-lisp-comment (format-time-string "%A, %B %-d %Y"))
          "\n\n"
          (shell-command-to-string recker/scratch-message-command)))

(defun recker/scratch-lisp-comment (text)
  "Turn text into a lisp comment."
  (with-temp-buffer
    (insert text)
    (let ((comment-start ";; "))
      (comment-region (point-min) (point-max)))
    (concat "\n" (buffer-string) "\n")
    (buffer-string)))

(defun recker/refresh-scratch-buffer ()
  "Redraw the *scratch* buffer."
  (interactive)
  (save-excursion
    (switch-to-buffer "*scratch*")
    (erase-buffer)
    (insert (recker/scratch-message))))

(setq initial-scratch-message (recker/scratch-message))

Where C-x p deletes the current buffer, I added my own function that deletes all buffers which you can call by C-x P. Just like my browser tabs, sometimes I get a little overwhelmed and I need a clean slate to focus.

(global-set-key (kbd "C-x k") 'kill-this-buffer)

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

(global-set-key (kbd "C-x P") 'recker/purge-buffers)

I don’t know what this mode does, but at this point I’m too afraid to ask (or take it out of my config).

(global-visual-line-mode)

In Emacs, you spend much of your time selecting things in the minibuffer. “Interactive Do” (IDO for short) can enhance this experience. IDO comes with Emacs, but I install some packages to display options vertically instead of horizontally, and also to plug the interface in to Imenu.

(defun recker/configure-ido ()
  (setq ido-enable-flex-matching t)
  (setq ido-everywhere t)
  (ido-mode t)
  (use-package ido-vertical-mode
    :ensure t
    :config (setq ido-vertical-define-keys 'C-n-and-C-p-only)
    :init (ido-vertical-mode))
  (use-package idomenu
    :ensure t
    :bind ("C-c i" . idomenu)))

(recker/configure-ido)

The M-x menu also carries a lot of weight in the Emacs workflow. Transparently swapping out this command with the smex package adds value to this interface without changing the intuitive experience.

(use-package smex
  :ensure t
  :init (smex-initialize)
  :bind (("M-x" . 'smex)
         ("M-X" . 'smex-major-mode-commands)))

For quickly jumping around a buffer, standard isearch can’t be beat. But as a small luxury, sometimes I use the swiper package to quickly fuzzy search a buffer. I bind this to a similar keystroke as isearch so it’s easy to remember.

(use-package swiper
  :ensure t
  :bind ("C-c s" . swiper))

Use company mode for autocomplete. Without a direct way to call company mode, this plugin feels more magical to me. But other language modes seem to know where to find it without any needed interference, so that’s good.

(use-package company
  :ensure t
  :init (add-hook 'after-init-hook 'global-company-mode))

Use projectile for moving around git repos. From the outside, this plugin feels huge and robust. Compared to everything it can do, I barely use it. I’m content to leverage the project wide file search with C-c p f and the compile interface with C-c p P (all the projectile commands fall under the same C-c p prefix).

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

Files

Unfortunately, Emacs litters the filesystem with “backup” files. I can appreciate that it’s trying to be helpful, but it drives me nuts so I turn it off. Another edge case - if a file changes while I’m looking at it, I make Emacs re-render the buffer live.

(setq make-backup-files nil)
(setq auto-save-default nil)
(global-auto-revert-mode t)

Speaking of backing up files, tell emacs version control to follow symlinks if the file is under version control.

(setq vc-follow-symlinks 't)

And while we’re at it, install magit for working with git. This is not an understatement - magit is truly a beautiful piece of software. The way I have it configured, you can open the interface with C-x g (it will open git for the current file or prompt you for a git project).

(use-package magit
  :ensure t
  :bind ("C-x g" . magit-status))

Emacs has a great file manager called dired. To activate it, visit a directory just as you would open a file. Not wanting to interfere with greatness, I make just a few changes to the default behavior. I like to hide hidden files by default (you can see these by pressing C-x M-o), hide the . and .. pointers that you see by default, and blow through extra confirmations when you delete a file with a visiting buffer.

(require 'dired-x)
(setq dired-use-ls-dired nil)
(setq dired-clean-confirm-killing-deleted-buffers nil)
(setq-default dired-omit-files-p t)
(setq dired-omit-files (concat dired-omit-files "\\|^\\..+$"))
(add-hook 'dired-mode-hook 'dired-omit-mode)

I make good use of the Emacs bookmarks system. To create a bookmark for a file, just press C-x r m. To visit a bookmark, press C-x r b (with my customization in place, you will have the chance to choose a bookmark with IDO fuzzy search, so it’s very convenient). On top of that, every file/directory created in the following paths automatically gets a bookmark entry. These entries blend seamlessly with your existing bookmarks.

  • ~/org"
  • ~/src/
  • ~/src/work
(require 'bookmark)
(require 'cl-lib)

(setq bookmark-save-flag 1)

(defun recker/list-bookmarks ()
  "List all bookmarks in alphabetical order, and filter out the junk entries I don't care about."
  (let ((junk-entries '("org-capture-last-stored")))
    (sort (cl-remove-if #'(lambda (b) (member b junk-entries))
                        (append (bookmark-all-names) ; actual saved bookmarks
                                ;; then all the dynamic ones
                                (recker/list-files-as-bookmarks "org/" ".org")
                                (recker/list-entries-as-bookmarks "src/")
                                (recker/list-entries-as-bookmarks "src/work/")))
          #'string<)))

(defun recker/list-entries-as-bookmarks (parent)
  "List all the entries in the PARENT directory as if they were bookmarks."
  (mapcar #'(lambda (n) (concat parent n))
          (cl-remove-if #'(lambda (f) (string-prefix-p "." f))
                        (directory-files (expand-file-name (concat "~/" parent))))))

(defun recker/list-files-as-bookmarks (parent pattern)
  "List all the files matching pattern as if they were bookmarks."
  (mapcar #'(lambda (s) (string-remove-prefix (expand-file-name "~/") s))
          (directory-files-recursively (expand-file-name (concat "~/" parent)) pattern nil)))

(defun recker/ido-bookmark-jump (bookmark)
  "Switch to bookmark BOOKMARK interactively using `ido'."
  (interactive (list (ido-completing-read "Bookmark: " (recker/list-bookmarks) nil t)))
  (if (member bookmark (bookmark-all-names))
      (bookmark-jump bookmark)
    ;; If it's not in the actual bookmark file, just treat the key
    ;; like a relative path (ex. src/work/azdrain => ~/src/work/azdrain)
    (find-file (expand-file-name (concat "~/" bookmark)))))

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

Shell

It has been said that Emacs makes a pretty good operating system but lacks a decent editor. Add to that, it makes a decent terminal emulator as well. Using these settings, mash the C-x t command to open a bash shell. Exiting the shell also closes the buffer. For a less standard but more amusing eshell variety, press C-c e. While eshell can’t render output from tools like top, you can call lisp functions and interact with remote file systems, which is kind of neat.

;; This tool feels controversial.  It's here simply to sync the PATH
;; environment variable from the system with what Emacs sees.  One day
;; I'll have the time and bravery to disable it and test things out.
(use-package exec-path-from-shell
  :ensure t
  :config (exec-path-from-shell-initialize))

;; C-x t to open terminal
(defun recker/ansi-term ()
  "Launch ansi-term with current shell."
  (interactive)
  (let ((shell (or (getenv "SHELL") "/bin/bash")))
    (ansi-term shell)))

(global-set-key (kbd "C-x t") 'recker/ansi-term)

;; C-c e to open eshell
(global-set-key (kbd "C-c e") 'eshell)

;; Automatically close the buffer when the shell exits
(defun recker/handle-term-exit (&optional process-name msg)
  (message "%s | %s" process-name msg)
  (kill-buffer (current-buffer)))

(advice-add 'term-handle-exit :after 'recker/handle-term-exit)

Programming

Emacs is or is about to adopt eglot as its official LSP implementation. For now I just make sure it’s installed. Some of the following language modes call out to this underlying process to handle their own smarts (so long as the underlying tool is installed). Since I don’t use LSP for every language I work with, I’ll annotate this with greater details where it applies. The greater majority of extra language modes provide nothing more than indentation defaults, syntax highlighting, and a mode bar label to simply remind you what you’re working on.

(use-package eglot
  :ensure t
  :config
  (setq eglot-autoshutdown 't)
  (setq eglot-autoreconnect nil)
  (setq eglot-confirm-server-initiated-edits nil))

For these more lightweight language modes, I setup flycheck. It will do its best to show red squiggly lines where your code doesn’t make sense according to it’s generic backends (I have disabled some notoriously buggy and obnoxious ones and I will not hesitate to add to this list).

(use-package flycheck
  :ensure t
  :init (global-flycheck-mode)
  :config (setq-default flycheck-disabled-checkers '(emacs-lisp-checkdoc ruby-rubocop)))

Enable EditorConfig if it’s there. I don’t like junking up projects with new dotfiles, so I might not always be the guy to add one of these. But EditorConfig is a decent project and enough of my coworkers use it to make it worth installing on my end.

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

Python

First, Disable flycheck and flymake (Emacs’ native python mode seems to always want to turn it on) and turn on eglot mode instead.

(defun recker/python-mode-hook ()
  ;; disable fly* bullshit
  (flymake-mode -1)
  (flycheck-mode -1))

(add-hook #'python-mode-hook #'recker/python-mode-hook)
(add-hook 'python-mode-hook 'eglot-ensure)

Next, setup eglot with a hierarchy of LSP programs it should prefer. Why so many? Some of these are slow to support newer versions of python, so you may have to occasionally spin the roulette wheel. Rest assured that when you find one that works, eglot should get in line.

(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs
               `(python-mode . ,(eglot-alternatives
                                 '(("pylsp")
                                   ("pyls" "--stdio")
                                   ("pyright-langserver" "--stdio")
                                   ("jedi-language-server"))))))

Now that eglot is happy, you have to setup your python environment (which ideally should be a virtualenv). I run this function on a venv path to symlink to a place where Emacs can find it and activate it for eglot.

(defun recker/python-workon ()
  (interactive)
  "Activate a python environment.  If it's not in the WORKON_HOME list, create a symlink to a venv."
  ;; TODO: install a LSP by prompt
  (let* ((workon-home (or (getenv "$WORKON_HOME") (expand-file-name "~/.virtualenvs")))
         (existing-venvs (directory-files workon-home nil directory-files-no-dot-files-regexp))
         (chosen-venv (completing-read "Python Environment: " existing-venvs nil 'confirm))
         (symlink-dest (concat (file-name-as-directory workon-home) chosen-venv)))
    (unless (member chosen-venv existing-venvs)
      (let ((symlink-src (expand-file-name (read-directory-name "Path to venv: "))))
        (make-symbolic-link symlink-src symlink-dest)))
    (pyvenv-workon chosen-venv)
    (message "activated python environment \"%s\"" chosen-venv)))

After creating the environment (but before opening a python file and triggering eglot), jump into the project and run this shell command.

./venv/bin/pip install pylsp isort pyflakes

Go

I’m definitely not a go expert, but given the language’s growing popularity I’m trying to lean into the ecosystem and improve my skills here. Thankfully it works pretty seamlessly with eglot so it doesn’t require a lot of code. This works as long as you run go install golang.org/x/tools/gopls@latest from a shell within in the project.

(use-package go-mode :ensure t)

(add-hook 'go-mode-hook 'eglot-ensure)

(defun recker/go-mode-hook ()
  (add-hook 'before-save-hook #'gofmt-before-save)
  (add-hook 'before-save-hook #'lsp-organize-imports t t))

(add-hook 'go-mode-hook 'recker/go-mode-hook)

Others

Here are all the random languages with lightweight modes. Not the most exciting block of code in my config, but it’s necessary.

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

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

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

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

(use-package dhall-mode
  :ensure t
  :mode "\\.dhall\\'"
  :config
  (setq dhall-format-at-save t
        dhall-format-arguments (\` ("--ascii"))
        dhall-use-header-line nil))

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

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

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

(use-package slime-company
  :ensure t
  :after (slime company)
  :config (setq slime-company-completion 'fuzzy
                slime-company-after-completion 'slime-company-just-one-space))

(use-package markdown-mode
  :ensure t
  :init (add-hook #'markdown-mode-hook 'eglot-ensure))

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

(use-package nftables-mode :ensure t)

(use-package nginx-mode :ensure t)

(use-package php-mode
  :ensure t)

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

(use-package rst
  :ensure t
  :mode (("\\.rst$" . rst-mode)))

;; ruby
(setq ruby-deep-indent-paren nil)

;; run this for eglot to work:
;; gem install solargraph
;; (add-hook 'ruby-mode-hook 'eglot-ensure)

(use-package terraform-mode
  :ensure t)

(use-package company-terraform
  :ensure t)

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

I don’t pretend to write a lot of C, let alone contribute to anything as import as Linux. But after stumbling on the very funny and sarcastic linux kernel style guide, I decided to honor this great writing by including the snippet in my config. Plus, in case I ever accidentally commit some code to the Linux kernel, maybe Linus will think I’m cool and offer to adopt me as his son.

;; C (from linux kernel standards)
(defun recker/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)))

(defun recker/c-mode-hook ()
  (c-add-style
   "linux-tabs-only"
   '("linux" (c-offsets-alist
              (arglist-cont-nonempty
               c-lineup-gcc-asm-reg
               recker/c-lineup-arglist-tabs-only))))
  (setq indent-tabs-mode t)
  (setq show-trailing-whitespace t)
  (c-set-style "linux-tabs-only"))

(add-hook 'c-mode-hook #'recker/c-mode-hook)

Email

Emacs has the built-in capability to send email. From anywhere, press C-x m to open the compose mail screen, do your business, then hit the ubiquitous C-c C-c to let it rip. Rather than sending the email directly, I configure Emacs to instead shell out to the CLI program msmtp, which is smart enough to use different settings based on the “From” address. I’ve tried a lot of solutions over the years, and I’ve settled on this solution as my favorite. If msmtp isn’t installed or if it’s configured wrongly, Emacs will throw a pretty obvious error message.

(setq smtpmail-smtp-service 587
      smtpmail-smtp-user user-mail-address
      smtpmail-smtp-server "smtp.gmail.com"
      send-mail-function 'smtpmail-send-it)
(setq message-send-mail-function 'message-send-mail-with-sendmail)
(setq sendmail-program "msmtp")
(setq mail-host-address "smtp.gmail.com")
(setq message-sendmail-f-is-evil 't)
(setq message-sendmail-extra-arguments '("--read-envelope-from"))

screenshots/email.png

I read email with Gnus. It is both the worst and best tool for the job. My own journey into Gnus, from what I hear, is typical. I opened it once, felt disgusted, closed it out for a few months and tried other things, then occasionally retreated to Gnus to see if it was really as bad as I remember. Curiosity (or Stockholm syndrome) eventually got the best of me, and I concluded it was both the worst and best option for what I needed to do.

Without further ado, here are my very minimal settings for Gnus. Let’s get all the tuning stuff out of the way.

;; keep passwords in ~/.password-store/authinfo.gpg (and work)
(setq auth-sources
      (list
       (concat (expand-file-name "~/.password-store/") "authinfo.gpg")
       (concat (expand-file-name "~/.password-store-work/") "authinfo.gpg")))

;; hide startup files in .emacs.d/gnus/
;; little known fact, there are an infinite number of gnus directories
;; and they WILL make their way to your home directory whether you
;; want it or not
(let ((gnus-dir (concat user-emacs-directory "gnus/")))
  (setq gnus-startup-file (concat gnus-dir "newsrc"))
  (setq gnus-home-directory (concat gnus-dir "gnus")
        nnfolder-directory (concat gnus-dir "gnus/Mail/archive")
        message-directory (concat gnus-dir "gnus/Mail")
        nndraft-directory (concat gnus-dir "gnus/Drafts")
        gnus-cache-directory (concat gnus-dir "gnus/cache")))

;; If you experience dribble, talk to your doctor.
(setq gnus-use-dribble-file nil)

;; set primary method to empty so the program doesn't absolutely
;; EXPLODE when you open it
(setq gnus-select-method '(nnml ""))

;; set topic mode (the only readable mode) as the default
(add-hook 'gnus-group-mode-hook 'gnus-topic-mode)

;; Don't move archived messages anywhere
(setq gnus-message-archive-group nil)

;; powerful placebo settings for faster perceived speed
(setq gnus-asynchronous t)
(setq gnus-use-cache t)
(setq gnus-check-new-newsgroups nil
      gnus-check-bogus-newsgroups nil)
(setq gnus-show-threads nil
      gnus-use-cross-reference nil
      gnus-nov-is-evil nil)
(setq gnus-check-new-newsgroups nil
      gnus-use-adaptive-scoring nil)

;; Look at this fucking variable lol
(setq gnus-summary-line-format "%U%R%z%I%(%[%4L: %-23,23f%]%) %s
")

;; Use this nerdy bullshit to save email addresseses for autocompletion
(use-package bbdb
  :ensure t
  :config (setq bbdb-file (concat user-emacs-directory "bbdb.el"))
  :init
  (bbdb-mua-auto-update-init 'message)
  (setq bbdb-mua-auto-update-p 'query)
  (add-hook 'gnus-startup-hook 'bbdb-insinuate-gnus))

;; auto filled messages look like shit on most normal mail clients, so
;; just turn it off to appease all the filthy casuals I email
(add-hook 'message-mode-hook #'turn-off-auto-fill)

Finally we’ve arrived at the backends. If you desire, you can hook it up to RSS and other fun backends. I just use mail, but I occasionally revisit this list when I want to play with something new.

(setq gnus-secondary-select-methods '())

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

;; work email
(add-to-list 'gnus-secondary-select-methods
             `(nnimap ,recker/work-mail-address
                      (nnimap-user ,recker/work-mail-address)
                      (nnimap-address "imap.gmail.com")
                      (nnimap-server-port "imaps")
                      (nnimap-stream ssl)
                      (nnmail-expiry-target ,(format "nnimap+%s:[Gmail]/Trash" recker/work-mail-address))
                      (nnmail-expiry-wait immediate)))

If all went according to plan, Gnus should be ready to use. Just run M-x gnus, and if all went according to plan, you should see something resembling Email folders. There are only a few remaining things that regrettably have to be done manually.

  1. Make your topics. From the screen, I struggle through the topic commands to separate the IMAP folders into personal and work. I then use U to “unsubscribe” from the ones I don’t care about (which really just hides them).
  2. Fix posting styles. With your cursor hovering on a topic or folder, press G c to open the customize menu (this is a useful interface, have a look around). From there I add “address” to personal and work topics as a posting style, this is needed for msmtp to correctly route to the right settings.

screenshots/gnus.png

Some basic usability tips.

  1. Open folders by hitting RET over the folder, open messages by hitting RET over the message
  2. Trash mail by pressing E (expire) to mark it, then q to exit the folder. Expiring is done in batches
  3. Archive mail by moving the message to the IMAP folder (B m, then choose the folder interactively).
  4. Compose a new message by pressing m at the topic screen. Depending on where your cursor is, the corresponding styles and settings will apply.

Org

Emacs ships with “org mode”, which can be thought of as markdown on steroids. I shift from periods of heavy usage to light usage depending on my mood. Truthfully, this is one of org mode’s greatest strengths: whether you decide to jot a few notes in a meeting or move your entire life into org files, org mode can help you achieve your goals. I recommend Rainor’s youtube series. You will grow to love his straight forward, well-organized presentation of the basics as well as his fantastic accent.

Inject some built-in libraries, load some modules, and enable some random settings.

(require 'org-tempo)

;; uncomment this to automatically commit attachments to a git repo
;; (require 'org-attach-git)

(setq org-modules '(ol-bbdb
                    ol-bibtex
                    ol-docview
                    ol-doi
                    ol-eww
                    ol-gnus
                    org-habit
                    ol-info
                    ol-irc
                    ol-mhe
                    ol-rmail
                    ol-w3m))

;; hack to fix yasnippet in org
(defun recker/fix-yas-in-org ()
  (setq-local yas-buffer-local-condition
              '(not (org-in-src-block-p t))))

(add-hook 'org-mode-hook #'recker/fix-yas-in-org)

(add-hook 'org-mode-hook #'turn-off-auto-fill)

(org-indent-mode 0)
(org-clock-persistence-insinuate)

(setq org-startup-with-inline-images nil)
(setq org-adapt-indentation nil)
(setq org-cycle-separator-lines -1)
(setq org-goto-auto-isearch nil)
(setq org-clock-persist 'history)
(setq org-log-into-drawer 't)
(setq org-todo-keywords '((sequence "TODO" "DONE")))

Though not required, it’s a good idea to make a directory for org files. I keep mine in ~/org.

(setq org-directory (expand-file-name "~/org"))

Org can run code snippets within your documents. Tumble down this rabbit hole, starting with this very popular blog post. Here is where I load the languages I use and assign them to the insert block command (C-c C--). Also, I don’t want org to confirm each time I run a code snippet - because life is too short for that.

(setq org-confirm-babel-evaluate nil)

(global-set-key (kbd "C-c C--") #'org-insert-structure-template)

;; this gives org permission to run these languages (it doesn't need
;; permissions for lisp)
(org-babel-do-load-languages 'org-babel-load-languages '((python . t)
                                                         (ruby . t)
                                                         (shell . t)))

;; this shows up in the menu prompt after hitting the hot key for
;; inserting a code block
(setq org-structure-template-alist '(("e" . "src emacs-lisp")
                                     ("p" . "src python")
                                     ("r" . "src ruby")
                                     ("b" . "src bash")
                                     ("d" . "src plantuml")
                                     ("x" . "example")))

I like to make diagrams within org using PlantUML. I download the plantuml.jar from the website and place it in ~/.plantuml/plantuml.jar. This plugin gives you a major mode where you can evaluate the diagram code, and it also incorporates with org mode if you give the snippet a :file <some/path>.png header and hit C-c C-c over the snippet.

(use-package plantuml-mode
  :ensure t
  :config
  (setq org-plantuml-jar-path "~/.plantuml/plantuml.jar")
  (setq plantuml-default-exec-mode 'jar)
  (setq plantuml-jar-path "~/.plantuml/plantuml.jar")
  :init
  (add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
  (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t))))

The capture feature is handy. From anywhere in emacs, just hit C-c c and a secondary menu will present your note taking templates. You can use this menu to squirrel away quick notes and thoughts in predefined places without stealing your focus. I use this tool for writing journals, blog posts, and work tasks (the blog post one requires a custom function).

(defun recker/blog-target ()
  "Opens today's blog entry."
  (find-file (expand-file-name (format-time-string "~/src/blog/entries/%Y-%m-%d.html")))
  (goto-char (point-min)))

(defun recker/blog-template ()
  "Return the metadata for today's blog post."
  (format-time-string "<!-- meta:title -->\n<!-- meta:banner %Y-%m-%d.jpg -->\n\n"))

(setq org-capture-templates
      '(("t" "todays tasks" entry (file "tasks.org") "* TODO %<%A, %B %d %Y> [/]\nSCHEDULED: %t")
        ("m" "miscellaneous task" entry (file "tasks.org") "* TODO %?\nSCHEDULED: %t")
        ("j" "journal entry" plain (file+olp+datetree "journal.org.gpg") "%^{Grattitude}\n\n%?")
        ("b" "blog entry" plain (function recker/blog-target) (function recker/blog-template) :immediate-finish t :jump-to-captured t)))

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

The agenda screen is is great. From anywhere in emacs just hit C-c a and you’ll be greeted by a secondary pop-up menu where you can mash a to view the default agenda or any other custom agendas you manage to string together. Items in these views act like hyperlinks to the original item in your notes (I have a small customization in these configs that jumps to these in narrowed view). I don’t have any custom agendas at work these days, but in the past I’ve used :personal: and :work: tags to make a personal and work agenda view. I left the code commented out for learning reasons.

(setq org-agenda-files `( ,org-directory ))
(setq org-agenda-file-regexp "\\`[^.].*\\.org\\\(\\.gpg\\\)?\\'")

(global-set-key (kbd "C-c a") 'org-agenda)
(setq org-agenda-start-with-follow-mode nil)

(setq org-agenda-skip-scheduled-if-done 't)
(setq org-agenda-skip-deadline-if-done 't)
(setq org-agenda-archives-mode nil)
(setq org-deadline-warning-days 3)
(setq org-agenda-span 2)


(defun recker/org-agenda-switch-to-narrowed-subtree ()
  (interactive)
  (org-agenda-switch-to)
  (org-narrow-to-subtree))

(add-hook 'org-agenda-mode-hook
          (lambda ()
            (local-set-key (kbd "RET") 'recker/org-agenda-switch-to-narrowed-subtree)))

;; Speed settings
(setq org-agenda-inhibit-startup t)
(setq org-agenda-use-tag-inheritance nil)
(setq org-agenda-ignore-properties '(effort appt stats category))

(defun recker/org-agenda-if-tag(tag)
  "Skip entries that are tagged TAG"
  (let* ((entry-tags (org-get-tags-at (point))))
    (if (not (member tag entry-tags))
        (progn (outline-next-heading) (point))
      nil)))

(setq org-agenda-custom-commands '())
;; '(("p" "personal agenda"
;;    ((agenda)
;;     (tags-todo "personal"))
;;    ((org-agenda-skip-function '(recker/org-agenda-if-tag "personal"))
;;     (org-agenda-overriding-header "Personal Agenda")))
;;   ("w" "work agenda"
;;    ((agenda)
;;     (tags-todo "work"))
;;    ((org-agenda-skip-function '(recker/org-agenda-if-tag "work"))))))

(setq org-use-tag-inheritance 't)
(setq org-agenda-tag-filter-preset '())

You can use C-c e to publish notes to various formats (plain text, HTML, and even PDF if you have latex installed). While not currently a critical part of my workflow, I occasionally export my notes just to see what they look like in HTML for vanity’s sake or as a fun party trick.

;; this package makes source code a lot prettier when explorted
(use-package htmlize :ensure t)

;; bigger projects can be defined here
(setq org-publish-project-alist '())

Functions

A junk drawer of Emacs functions that I use, despite having nowhere to categorize them.

(defun recker/add-p-tags-to-buffer ()
  "Automatically wrap all paragraphs in buffer in <p></p> tags."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "\\(\\`\\|\n\n+\\)\\([^< $\n]\\)" nil t)
      (replace-match "\\1<p>\\2" t))
    (goto-char (point-min))
    (while (re-search-forward "\\([^>}\n]\\)\\(\n\n+\\|\n\\'\\)" nil t)
      (replace-match "\\1</p>\\2" t))))

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

About

My life is dope and I have dope emacs configs

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published