My init.el
file uses org-babel-load-file
to load the Emacs Lisp source code blocks in this README. In other words, this README that you’re looking at now is my Emacs configuration.
If the variables I’m setting aren’t clear what they’re used for, use \C-h v
and type it in to read the docstring. I’ll make sure to mention the interesting parts though.
I want my customization information going to a file other than the emacs config file. I don’t like the constant churn in my main config file when I make a change through customize.
(setf custom-file "~/.emacs.d/emacs-custom.el")
(load custom-file t)
After struggling with managing packages through my Emacs configuration, I decided to manage Emacs packages through Nix. Here is my nix expression that handles it all.
A nice benefit of this approach (in addition to all the benefits of nix) is that every package I choose is installed when I use nix to install Emacs, so I can remove all the code managing packages and simplify my Emacs configuration.
Gotta turn off tabs, also enable auto-revert-mode so changes to the files outside of emacs update the buffers. Plus some other various stuff.
(global-hl-line-mode t)
(global-auto-revert-mode t)
(global-prettify-symbols-mode t)
(dumb-jump-mode)
(setf indent-tabs-mode nil
initial-buffer-choice (lambda ()
(org-agenda nil "a")
(delete-other-windows))
vc-follow-symlinks t
confirm-kill-emacs 'y-or-n-p
backup-directory-alist '(("." . "~/.emacs.d/backup"))
abbrev-file-name "~/org/abbrev_defs")
(quietly-read-abbrev-file nil)
(add-to-list 'exec-path "~/.nix-profile/bin")
(add-to-list 'load-path "~/.emacs.d/lisp")
(setq tab-always-indent 'complete)
(add-to-list 'Info-directory-list (expand-file-name "~/org/info"))
(require 'server)
(require 'org-man)
(pdf-tools-install)
(unless (server-running-p)
(server-start))
This sets up japanese input within emacs. You probably don’t need this if you have it set up through your OS?
(setf default-input-method "japanese")
(global-set-key (kbd "C-x g") 'magit-status)
(global-set-key (kbd "C-x M-g") 'magit-dispatch-popup)
(global-set-key "\C-cl" 'org-store-link)
(global-set-key "\C-ca" 'org-agenda)
(global-set-key "\C-cc" 'org-capture)
(global-set-key "\C-cb" 'org-iswitchb)
(global-set-key "\C-cj" (lambda () (interactive) (org-clock-jump-to-current-clock)))
(global-set-key "\C-s" 'swiper)
(global-set-key (kbd "C-S-c C-S-c") 'mc/edit-lines)
(global-set-key (kbd "C->") 'mc/mark-next-like-this)
(global-set-key (kbd "C-<") 'mc/mark-previous-like-this)
(global-set-key (kbd "C-c C-<") 'mc/mark-all-like-this)
(require 'spaceline-config)
(setf powerline-default-separator 'wave)
(when (eq system-type 'darwin) ; fix for spaceline on mac
(setf ns-use-srgb-colorspace nil))
(spaceline-spacemacs-theme)
(load-theme 'zenburn t)
(show-paren-mode t)
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(fringe-mode 1)
Turn the clock on in the mode-line. I tend to never leave Emacs, so I’d lose track of time otherwise! By default it also shows the load-average, but I’m not really worried about that so I turned it off.
(setf display-time-default-load-average nil)
(display-time-mode)
(setf user-full-name "Caleb Gossler"
user-mail-address "[email protected]"
calendar-latitude 47.4
calendar-longitude -122.3
calendar-location-name "Seattle, WA")
This file is where I store words I want added to my dictionary so they aren’t marked as misspelled.
(setf ispell-personal-dictionary "~/org/dictionary")
(setf diary-file "~/org/diary"
appt-message-warning-time 15
diary-number-of-entries 3
appt-display-diary nil)
(appt-activate 1)
(autoload 'LilyPond-mode "lilypond-mode")
(add-to-list 'auto-mode-alist '("\\.ly$" . LilyPond-mode))
(add-hook 'LilyPond-mode-hook (lambda () (turn-on-font-lock)))
Auth sources let you store credentials for services you use in emacs. This way you don’t need to keep passwords in plain text in your config.
(setf auth-sources
'((:source "~/org/authinfo.gpg")))
I’m trying out org-drill for creating and studying flash cards. It looks like if I add it to the org-modules, I need to require cl
before it gets loaded.
The org-drill-all
command scans for flashcards throughout all my notes and starts an org-drill session.
(defun org-drill-all ()
(interactive)
(org-drill
(directory-files-recursively "~/org/notes/" "\.org$")))
This lets me ignore headers when exporting, without ignoring the contents under it.
(require 'ox-extra)
(ox-extras-activate '(ignore-headlines))
Now that’s loaded so we can start setting some options.
(add-hook 'org-mode-hook 'visual-line-mode)
(add-hook 'org-mode-hook 'flyspell-mode)
(add-hook 'org-mode-hook 'org-display-inline-images)
(add-hook 'org-babel-after-execute-hook (lambda ()
(when org-inline-image-overlays
(org-redisplay-inline-images))))
(require 'cl)
(require 'org-drill)
(require 'org-habit)
(load "auctex.el")
(setf org-agenda-files '("~/org/agenda")
org-agenda-include-diary t
org-refile-targets '((org-agenda-files :maxlevel . 2))
org-startup-indented t
org-agenda-span 'day
org-agenda-todo-ignore-scheduled 'future
org-log-into-drawer t
org-clock-idle-time 10
org-return-follows-link t
org-special-ctrl-a/e t
org-pretty-entities t
org-pretty-entities-include-sub-superscripts t
org-agenda-skip-scheduled-if-deadline-is-shown t
org-drill-learn-fraction 0.3
org-drill-add-random-noise-to-intervals-p t
org-drill-leech-method 'warn
org-log-done 'time
org-latex-create-formula-image-program 'dvipng)
(plist-put org-format-latex-options :scale 2)
If you use org-agenda-text-search-extra-files
, You can use the agenda view search facility to do text searching in org files other than your agenda files. This enables evernote-like search features in Emacs!! I set it to include all org files (recursively) in my ~/org/notes/
directory.
Note: \C-a s
brings up the search prompt in the minibuffer.
(setf org-agenda-text-search-extra-files (directory-files-recursively "~/org/notes/" "\.org$"))
Setting up keyword workflows.
(setf org-todo-keywords
'((sequence "TODO(t)" "IN PROGRESS(i)" "ON HOLD(h)" "IN REVIEW(r)" "FOLLOW-UP(f)" "BLOCKED(b)" "RELEASE(e)" "|" "DONE(d!)" "CANCELLED(c!)")))
(setf org-clock-persist 'history)
(org-clock-persistence-insinuate)
I have a file of babel functions that I want available in every org file.
(org-babel-lob-ingest "~/org/babel/library.org")
This enables execution of various code blocks
(setf org-ditaa-jar-path "/run/current-system/sw/lib/ditaa.jar"
org-confirm-babel-evaluate nil)
(org-babel-do-load-languages
'org-babel-load-languages
'((ditaa . t)
(sql . t)
(dot . t)
(racket . t)
(shell . t)
(python . t)
(lilypond . t)
(latex . t)
(gnuplot . t)))
(setq org-babel-racket-command "racket")
If any capture doesn’t specify a target, fall back to inbox.org
.
(setf org-default-notes-file "~/org/agenda/inbox.org")
(setf org-capture-templates
'(("c" "Clock into new")
("ct" "Task" entry (file+headline "~/org/agenda/inbox.org" "Inbox")
"* TODO %?\nSCHEDULED: %T Created: %U\n Context: %a\n %i" :clock-in t :clock-keep t)
("cm" "Meeting" entry (file+datetree "~/org/agenda/meetings.org")
"* %?\n** Details\n + *Agenda*:\n + *Attendees*:\n" :clock-in t :clock-keep t)
("t" "New Task" entry (file+headline "~/org/agenda/inbox.org" "Inbox")
"* TODO %?\n Created: %U\n Context: %a\n %i")
("j" "New Journal Entry" entry (file+datetree "~/org/agenda/journal.org.gpg")
"* %? %^g\n\nPosted At: %U" :empty-lines-after 1 :kill-buffer t)
("l" "New Log Entry" entry (file+datetree "~/org/agenda/log.org.gpg")
"* %? \nPosted At: %U" :empty-lines-after 1 :kill-buffer t)
("r" "Random" entry (file "~/org/agenda/random.org.gpg")
"* %?\nCreated At: %U" :empty-lines-after 1 :kill-buffer t)
("n" "Add Note to Clocked in Entry" item (clock)
"+ %i%? (%<%r>)")
("b" "Add Checkbox to Clocked in Entry" item (clock)
"+ [ ] %? (%<%r>)" :prepend t)
("a" "Add Task to Clocked in Entry" entry (clock)
"* TODO %?" :prepend t)))
(define-key global-map "\C-cn"
(lambda () (interactive) (org-capture nil "n")))
This is a helper function that lets emacsclient start a new frame and select a capture template. There’s also an advice function to close the frame after the capture has been finalized.
The emacs client command is emacsclient -e "(start-capture \"t\")"
(defun start-capture (template-key)
"Start capture with the template assigned to TEMPLATE-KEY"
(make-frame '((alpha . 80)(height . 10)
(top . -1)(left . -10)(autoraise . t)
(title . "Capture")(name . "captureframe")
(minibuffer . nil)))
(select-frame-by-name "captureframe")
(org-capture nil template-key)
(delete-other-windows))
(defadvice org-capture-finalize (after delete-capture-frame activate)
"Advise capture-finalize to close the frame if it is the capture frame"
(if (equal "captureframe" (frame-parameter nil 'name))
(delete-frame)))
(setf org-agenda-custom-commands
'(("a" "Combined Agenda"
((agenda)
(tags-todo "+inbox" ((org-agenda-overriding-header "Inbox:")))))
("t" "Study"
((agenda)
(todo "READING" ((org-agenda-overriding-header "Currently Reading")))
(todo "PAUSED" ((org-agenda-overriding-header "On Hold")))
(todo "TO-WATCH" ((org-agenda-overriding-header "Videos to Watch")))
(todo "RESEARCH"))
((org-agenda-category-filter-preset '("+study"))))
("w" "Work"
((agenda)
(todo "BLOCKED" ((org-agenda-overriding-header "Blocked:")))
(tags-todo "+inbox" ((org-agenda-overriding-header "Inbox:")))
(todo "REVIEW" ((org-agenda-overriding-header "Code Reviews:")))
(todo "WAITING" ((org-agenda-overriding-header "Waiting:")))
(todo "IN-PROGRESS" ((org-agenda-overriding-header "In Progress:")))
(todo "RESEARCH" ((org-agenda-overriding-header "Research:")))
(todo "READING" ((org-agenda-overriding-header "Reading:")))
(todo "TO-READ" ((org-agenda-overriding-header "To Read:")))
(todo "TODO" ((org-agenda-overriding-header "Backlog:"))))
((org-agenda-category-filter-preset '("+work"))))))
(setq org-publish-project-alist
'(("homepage"
:base-directory "~/org/website/"
:publishing-directory "~/website"
:publishing-function org-html-publish-to-html)
("notes"
:base-directory "~/org/website/notes"
:publishing-directory "~/website/notes"
:publishing-function org-html-publish-to-html
:makeindex t
:auto-sitemap t
:sitemap-filename "index.org"
:sitemap-title "My Notes"
:sitemap-format-entry my-org-publish-sitemap
:sitemap-style list
:recursive t)))
Dired renders a buffer that it builds off of a call to ls
. Here we can customize the switches passed to it.
dired-dwim-target
tells dired to try to guess a default target directory for file operations. This means if there is a Dired buffer displayed in the next window, use that as the target. Convinient when doing operations between directories.
(setf dired-listing-switches "-lh"
delete-by-moving-to-trash t
dired-dwim-target t)
By default, dired shows permissions, user and group, file size, and dates. I prefer a cleaner view, so this enables dired-hide-details-mode
. You can toggle it on and off with (
when the extra details are needed.
(add-hook 'dired-mode-hook 'dired-hide-details-mode)
Dired-X adds some nice features, one of them is doing file operations async. A must have for doing large/over-the-network file operations
(add-hook 'dired-load-hook
(lambda ()
(load "dired-x")
(dired-async-mode)))
It’s very convenient to be able to control music from within Emacs. I use it mostly to play streams.
(require 'emms-setup)
(require 'emms-streams)
(require 'emms-stream-info)
(setq emms-directory "~/org/emms"
emms-stream-default-action "play"
emms-stream-info-backend 'mplayer
emms-stream-bookmarks-file "~/org/emms/streams"
emms-mode-line-format " 𝄞 ")
(emms-minimalistic)
(emms-default-players)
(emms-mode-line-enable)
(advice-add 'emms-stream-info-mplayer-backend
:override
(lambda (url)
"The original function isn't working, using this temporarily until I figure it out."
(condition-case excep
(call-process "mplayer" nil t nil
"-msglevel" "decaudio=-1:cache=-1:statusline=-1:cplayer=-1" "-cache" "180"
"-endpos" "0" "-vo" "null" "-ao" "null" "-playlist"
url)
(file-error
(error "Could not find the mplayer backend binary")))))
Use gpg2 instead of gpg
(setf epg-gpg-program "gpg2")
(setf ivy-use-virtual-buffers t)
(setf magit-completing-read-function 'ivy-completing-read)
(setf ivy-count-format "(%d/%d) ")
(ivy-mode 1)
I use this to connect to google hangouts/talk. It doesn’t offer all the features but It’s nice being able to chat in emacs.
(setf jabber-alert-presence-hooks nil
jabber-show-resources nil
jabber-auto-reconnect t
jabber-history-enabled t
jabber-roster-show-title nil
jabber-roster-line-format " %c %-25n %u %-8s %S"
jabber-alert-message-wave "~/.emacs.d/data/sound.wav"
; jabber-message-alert-same-buffer nil
jabber-account-list '(("[email protected]"))
jabber-alert-message-hooks '(jabber-message-notifications
jabber-message-echo
jabber-message-scroll
jabber-message-wave))
After a few tries, I’ve settled on using mu4e for reading email. mu4e leverages mu, which is a set of tools to index and search emails stored in Maildir format.
In order for mu to index that email, it has to exist in a Maildir
somewhere on your system. I’m using mbsync
for that. mbsync
synchronizes IMAP4 and Maildir mailboxes. It propogates new mail, deletions, etc. both ways. In addition to mbsync
, offlineimap
seems to be another popular choice.
I have this setup for sending/receiving email for two accounts, my work and personal. With mu4e, you can do this with mu4e “contexts.”
Setting it all up is not as bad as it sounds. And in my opinion it offers a better experience than any other email client I’ve used, and it’s all within Emacs!
For sending mail, I’m using Message
, the Emacs message composition mode. After this is set up, you can use Message
mode (\C-x m
) to send outgoing email.
One complication with my setup is that I want to be able to send mail through two SMTP accounts. mu4e makes this easy to do with mu4e contexts. There are some variables you need to set to your SMTP server’s info, and when you switch contexts in mu4e it will adjust those variables for you.
Here I’m setting some variables that are consistent across both my SMTP accounts. Below in the mu4e-contexts
setup, you’ll see where I set the individual values for smtpmail-smtp-server
on both contexts.
(setf send-mail-function 'smtpmail-send-it
smtpmail-stream-type 'starttls
smtpmail-smtp-service 587
message-kill-buffer-on-exit t)
For authentication, the first time you send mail, Emacs will prompt you for your username and password. By default, it will save it in your authinfo file so you probably want your authinfo encrypted (which is dead simple using EasyPG, a built-in Emacs package).
Or, you can add it manually yourself by appending a line similar to the following to your authinfo (adding your username and password):
machine smtp.gmail.com login <your username>@gmail.com port 587 password <your password>
For gmail, you should have two factor authentication turned on, and generate an app specific password.
Most of these variables are self-explanitory. Some make mu4e work better with gmail, and are explained in the mu4e FAQ. The missing part here is my mbsync configuration file. I’ll see if I can get that included here at some point.
(require 'mu4e)
(require 'org-mu4e)
(add-hook 'mu4e-compose-mode-hook 'auto-fill-mode)
(global-set-key (kbd "C-c m") 'mu4e)
(setf mu4e-maildir "~/.mail"
mu4e-view-show-images t
mu4e-update-interval 300
mu4e-view-show-addresses t
mu4e-hide-index-messages t
mu4e-decryption-policy 'ask
mu4e-compose-format-flowed t
mu4e-context-policy 'ask-if-none
mu4e-get-mail-command "mbsync -a"
mu4e-change-filenames-when-moving t
mu4e-compose-context-policy 'ask-if-none
mu4e-maildir-shortcuts '(("/personal/inbox" . ?i)
("/work/inbox" . ?w))
mu4e-user-mail-address-list '("[email protected]"
"[email protected]"
"[email protected]")
mu4e-headers-fields '((:human-date . 15)
(:flags . 6)
(:from . 30)
(:thread-subject . nil)))
(setf mu4e-contexts
`( ,(make-mu4e-context
:name "Personal"
:match-func (lambda (msg)
(when msg
(mu4e-message-contact-field-matches
msg :to "gmail\.com")))
:vars '( ( user-mail-address . "[email protected]" )
( user-full-name . "Caleb Gossler" )
( smtpmail-smtp-server . "smtp.gmail.com")
( mu4e-compose-signature . "Caleb Gossler\nPGP: 94EE 36DD")
( mu4e-trash-folder . "/personal/trash")
( mu4e-sent-folder . "/personal/sent")
( mu4e-drafts-folder . "/personal/drafts")
( mu4e-refile-folder . "/personal/archive")
( mu4e-sent-messages-behavior . delete) ;gmail handles sent messages
( mu4e-headers-skip-duplicates . t)))
,(make-mu4e-context
:name "Work"
:match-func (lambda (msg)
(when msg
(mu4e-message-contact-field-matches
msg :to "motivity\.net")))
:vars '( ( user-mail-address . "[email protected]" )
( user-full-name . "Caleb Gossler" )
( smtpmail-smtp-server . "smtp.gmail.com")
( mu4e-compose-signature . "Caleb Gossler\nSoftware Engineer")
( mu4e-trash-folder . "/work/trash")
( mu4e-sent-folder . "/work/sent")
( mu4e-drafts-folder . "/work/drafts")
( mu4e-refile-folder . "/work/archive")
( mu4e-sent-messages-behavior . delete)
( mu4e-headers-skip-duplicates . t)))))
Get a desktop notification on update.
(add-hook 'mu4e-index-updated-hook
(defun new-mail-notification ()
(require 'notifications)
(notifications-notify :title "Email Updated")))
This hook seems to help when sending email, preventing newlines from appearing in paragraphs.
(add-hook 'mu4e-compose-mode-hook (lambda ()
(setf use-hard-newlines nil)))
(autoload 'bbdb-insinuate-mu4e "bbdb-mu4e")
(bbdb-initialize 'message 'mu4e)
(setf bbdb-file "~/org/bbdb.gpg")
(setq bbdb-mail-user-agent (quote message-user-agent))
(setq mu4e-view-mode-hook (quote (bbdb-mua-auto-update visual-line-mode)))
(setq mu4e-compose-complete-addresses nil)
(setq bbdb-mua-pop-up t)
(setq bbdb-mua-pop-up-window-size 5)
(add-hook 'racket-mode-hook
(lambda ()
(define-key racket-mode-map (kbd "C-c r") 'racket-run)))
(add-hook 'racket-mode-hook #'racket-unicode-input-method-enable)
(add-hook 'racket-repl-mode-hook #'racket-unicode-input-method-enable)
This function has ERC connect to every IRC network entry in your authinfo file.
Each line should look something like:
machine irc.freenode.net login mynick port irc password mypass
(defun irc-connect-all ()
(interactive)
(require 'auth-source)
(let ((auth (auth-source-search :port "irc" :max 10 :requires '(user secret host))))
(dolist (login auth)
(let ((pass (funcall (plist-get login :secret)))
(nick (plist-get login :user))
(host (plist-get login :host)))
(erc :server host :nick nick :password pass)))))
ERC Settings
(setf erc-hide-list '("JOIN" "PART" "QUIT")
erc-rename-buffers t
erc-kill-server-buffer-on-quit t
erc-modules '(autojoin button completion
fill irccontrols list log
match menu move-to-prompt
netsplit networks noncommands
notify notifications readonly
ring stamp spelling track)
erc-log-mode t
erc-log-insert-log-on-open t
erc-log-write-after-insert t
erc-log-channels-directory "~/org/irc-logs")
(elfeed-org)
(elfeed-goodies/setup)
(setf elfeed-db-directory "~/org/elfeed-db"
elfeed-goodies/entry-pane-position 'bottom
rmh-elfeed-org-files '("~/org/rss.org"))
- Sometimes I export an org document to UTF plain text, but need to paste it into an email. This is helpful to get rid of “fill”
(defun quit ()
"This will quit emacs and kill emacs server"
(interactive)
(save-some-buffers)
(kill-emacs))
(defun unfill-paragraph ()
(interactive)
(let ((fill-column (point-max)))
(fill-paragraph nil)))
(defun unfill-region (start end)
(interactive "r")
(let ((fill-column (point-max)))
(fill-region start end nil)))
- Let’s play zork!
(defun zork ()
"Starts a game of Zork."
(interactive)
(require 'malyon)
(malyon "~/.emacs.d/games/zork1.z5"))
(defun spider ()
"Starts a game of 'Spider and Web'."
(interactive)
(require 'malyon)
(malyon "~/.emacs.d/games/spider.z5"))
(add-hook 'after-init-hook 'global-company-mode)
(add-hook 'message-mode-hook 'turn-on-orgtbl)
(add-hook 'message-mode-hook 'turn-on-orgstruct++)
(add-hook 'sql-interactive-mode-hook (lambda ()
(toggle-truncate-lines t)))
(setq yas-snippet-dirs
'("~/.emacs.d/snippets"))
(yas-global-mode 1)
Stolen from here
(defun eshell-here ()
"Opens up a new shell in the directory associated with the
current buffer's file. The eshell is renamed to match that
directory to make multiple eshell windows easier."
(interactive)
(let* ((parent (if (buffer-file-name)
(file-name-directory (buffer-file-name))
default-directory))
(height (/ (window-total-height) 3))
(name (car (last (split-string parent "/" t)))))
(split-window-vertically (- height))
(other-window 1)
(eshell "new")
(rename-buffer (concat "*eshell: " name "*"))
;;(insert (concat "ls"))
(eshell-send-input)))
(global-set-key (kbd "C-x C-b") 'ibuffer)
(setq ibuffer-saved-filter-groups
'(("default"
("Dired" (mode . dired-mode))
("Magit" (name . "*magit"))
("Mail" (or (name . "*mu4e*")
(mode . mu4e-compose-mode)))
("ERC" (mode . erc-mode))
("Elfeed" (name . "*elfeed"))
("Help" (or
(name . "*Help*")
(name . "*Apropos*")
(name . "*info*")))
("Org" (or (mode . org-mode)
(mode . org-agenda-mode))))))
(add-hook 'ibuffer-mode-hook
(lambda ()
(ibuffer-auto-mode 1)
(ibuffer-switch-to-saved-filter-groups "default")))
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
(setq nov-text-width 100)