20 KiB
DOOM Emacs Configuration
- Basics
- User interface
- Keybindings
- Weather
- Music
- RSS
- Formatting
- Org-mode
- Novel
- Eshell
- Coding
- Mastodon
- Slack
- Beancount
- Pandoc
- Magit
- Openwith
- Reveal.js export
This configuration of Emacs is highly opinionated.
Basics
My identity. Used in a handful of places in Emacs to pre-populate authorship and such.
(setq user-full-name "Colin Powell"
user-mail-address "colin@unbl.ink")
From a friend at Discord
(setq ivy-read-action-function #'ivy-hydra-read-action)
User interface
Fonts and themes
I change my default theme almost as often as the weather. I tend to revert back to Doom One most of the time, but I like the Kaolin themes, as well as Nimbus for it's retro charm. Nimbus just doesn't look great when I'm tired though.
(setq doom-theme 'farmhouse-dark
doom-font (font-spec :family "Go Mono" :size 13 :weight 'regular)
doom-big-font (font-spec :family "Go Mono" :size 16 :weight 'regular)
doom-variable-pitch-font (font-spec :family "Overpass" :size 13))
Borders
Barring the unfortunate end of X11 development and my eventual transition to Wayland and sway, you can pry i3wm from my cold, dead hands. One problem, however is that when you're trying your best to rice up i3, Emacs needs a padded border.
;; Applies to current frame
(set-frame-parameter nil 'internal-border-width 10) ; applies to the current frame
;; If we create new frames (via emacsclient) this will do the trick
(add-to-list 'default-frame-alist '(internal-border-width . 10))
Fringe
(nyan-mode) ;; progress in the form of a rainbow cat.
(add-hook 'after-init-hook #'global-emojify-mode) ;; emojis?!
(add-hook 'prog-mode-hook #'goto-address-mode) ;; linify links!
Search
(setq eww-search-prefix "https://search.unbl.ink/?q=")
Keybindings
(map! ;; Easier window movement
:n "C-h" 'evil-window-left
:n "C-j" 'evil-window-down
:n "C-k" 'evil-window-up
:n "C-l" 'evil-window-right
(:map evil-treemacs-state-map
"C-h" 'evil-window-left
"C-l" 'evil-window-right)
:leader
(:prefix "f"
:desc "Find file in dotfiles" "t" #'+hlissner/find-in-dotfiles
:desc "Browse dotfiles" "T" #'+hlissner/browse-dotfiles)
(:prefix "o"
:desc "(H)ckrnews" "H" #'hackernews
:desc "(R)SS" "R" #'=rss
:desc "(M)ail" "M" #'=notmuch
:desc "(L)obste.rs" "L" #'ivy-lobsters)
(:prefix "b"
:desc "Black format buffer" "f" #'blacken-buffer
:desc "isort buffer" "I" #'py-isort-buffer
:desc "Links in buffer" "l" #'ace-link-org)
(:prefix "s"
:desc "Copy link hints" "c" #'link-hint-copy-link
:desc "Search the web" "w" #'web-search
:desc "Goto URL in eww" "u" #'eww-browse-url
:desc "Search in eww" "3" #'eww-search-words
:desc "Search all the things" "g" #'deadgrep))
Weather
Wttrin is a pretty funny way to check weather. I like that you can copy and paste fun a ASCII representations of the day's weather. It's not terribly accurate though.
(setq wttrin-default-cities '("Castine, ME" "San Francisco" "Thessaloniki"))
(setq wttrin-default-accept-language '("Accept-Language" . "en-US"))
(map!
(:leader
(:prefix "o"
:desc "(W)eather" "W" #'wttrin)))
Music
Right now, just make sure I can connect to my local Mopidy server via MPDel.
(setq libmpdel-hostname "mpd.unbl.ink")
(defun mpdel-playlist-play ()
"Start playing the song at point."
(interactive)
(if (derived-mode-p 'mpdel-playlist-current-playlist-mode)
(libmpdel-play-song (navigel-entity-at-point))
(mpdel-core-insert-current-playlist)))
(map! :leader
(:prefix "-"
:desc "MPD Open playlist" "-" #'mpdel-playlist-open
:desc "MPD Remove at point" "d" #'mpdel-playlist-delete
:desc "MPD Start at point" "s" #'mpdel-playlist-play
:desc "MPD Next track" "n" #'libmpdel-playback-next
:desc "MPD Previous track" "p" #'libmpdel-playback-previous))
RSS
Here we’re going to use Miniflux as a back-end store for our RSS feeds.
(setq elfeed-protocol-fever-maxsize 100)
(setq elfeed-feeds '(("fever+https://secstate@rss.unbl.ink"
:api-url "https://rss.unbl.ink/fever/"
:password "delegator flaxseed request washer"
:autotags '(("rss.unbl.ink")))))
;(setq elfeed-protocol-log-trace t)
(setq elfeed-protocol-fever-maxsize 50)
;(setq elfeed-log-level 'debug)
(elfeed-protocol-enable)
(map! :leader
(:prefix "r"
:desc "Open Elfeed" "r" #'elfeed
:desc "Update Elfeed" "u" #'elfeed-update))
;; Schedule feed update for every 15 minutes
(run-at-time 300 300
(lambda () (when (= elfeed-curl-queue-active 0)
(elfeed-update))))
Then we’ll setup some nice defaults and font settings for viewing feeds in Elfeed.
(setq elfeed-search-filter "@2-days-ago +unread")
(defun elfeed-search-format-date (date)
(format-time-string "%Y-%m-%d %H:%M" (seconds-to-time date)))
; Serif font in Elfeed
(add-hook! 'elfeed-mode-hook 'variable-pitch-mode)
(add-hook! 'elfeed-show-mode-hook (text-scale-set 1.2))
Formatting
Here we define the opposite function to fill-paragraph and then map it to Meta-z, which helps when undoing fill paragraph moves.
(defun unfill-paragraph ()
"Takes a multi-line paragraph and makes it into a single line of text."
(interactive)
(let ((fill-column (point-max)))
(fill-paragraph nil)))
(define-key global-map "\M-z" 'unfill-paragraph)
PlantUML is pretty fantastic. This enables a mode for editing plantuml files in Emacs.
;; PlantUML is awesome for quick diagrams
(add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode))
Handy clock to look up what timezone my co-workers are in!
;; Timezone location strings at http://worldtime.io
(setq display-time-world-list '(("America/Los_Angeles" "San Francisco")
("America/Tegucigalpa" "Tegucigalpa")
("America/New_York" "New York")
("Europe/London" "London")
("Europe/Warsaw" "Warsaw")
("Europe/Kiev" "Lviv")))
Vale linter, a more up to date proselint!
How I went about installing Vale
(flycheck-define-checker vale
"A checker for prose"
:command ("vale" "--output" "line"
source)
:standard-input nil
:error-patterns
((error line-start (file-name) ":" line ":" column ":" (id (one-or-more (not (any ":")))) ":" (message) line-end))
:modes (markdown-mode org-mode text-mode)
)
(add-to-list 'flycheck-checkers 'vale 'append)
(setq +format-on-save-enabled-modes
'(not emacs-lisp-mode ; elisp's mechanisms are good enough
sql-mode ; sqlformat is currently broken
tex-mode ; latexindent is broken
org-mode
latex-mode))
Org-mode
Basic configuration
A handful of mods here clean up org mode. Line numbers don’t mean much when you’re folding and unfolding all the time. I also really enjoy the typography of a serif font when I’m writing a lot of words.
(add-hook 'org-mode-hook #'doom-disable-line-numbers-h)
(after! org
(setq org-directory (expand-file-name "~/var/org/")
org-agenda-files (file-expand-wildcards "~/var/org/2021-*")
org-pretty-entities t
org-agenda-dim-blocked-tasks nil
org-log-done 'time
org-hide-emphasis-markers t
org-fontify-whole-heading-line t
org-fontify-done-headline t
org-fontify-quote-and-verse-blocks t
org-ellipsis "…"
org-image-actual-width '(600)
org-protocol-default-template-key "l"
org-capture-templates
'(("i" "Send to inbox" entry (file "~/var/org/index.org")
"* TODO %?\n")
("l" "Link" entry (file "~/var/org/index.org")
"* TODO %a\n %?\n %i"))
org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "WAITING(w)" "|" "DONE(d)"))
org-modules '(ol-eshell
ol-notmuch
ob-eval
ob-exp
ob-http
org-id)))
;; Don’t display git gutter in org mode
;; With tree folding, it's pretty much useless
(after! git-gutter
(setq git-gutter:disabled-modes '(org-mode image-mode)))
;; Refiling
(setq org-refile-targets '(("~/var/org/index.org" :maxlevel . 9)))
(setq org-outline-path-complete-in-steps nil) ; Refile in a single go
(setq org-refile-use-outline-path t) ; Show full paths for refiling
Key bindings
I add only two custom mappings to the default org mode maps, a shortcut to my Inbox file and a quick way to save all open org mode files.
(setq +inbox-file "~/var/org/index.org")
(defun +open-inbox-file ()
(interactive)
"Opens the inbox file"
(find-file +inbox-file))
(map!
:leader
:desc "Open inbox" "I" #'+open-inbox-file
:desc "Save all org buffers" "A" #'org-save-all-org-buffers)
Agenda configuration
(setq org-agenda-span 3
org-agenda-start-day "1d")
;(defun +show-agenda ()
; (interactive)
; (delete-other-windows)
; (with-popup-rules! nil
; (org-agenda-list)
; (calendar))
; (other-window 1)
; (split-window-vertically)
; (other-window 1)
; (find-file +todo-file))
Org-roam
I am absolutely in love with Org-roam. Everything about it makes taking notes easier. I just need to level up with Zettels and web publishing of my notes.
(setq org-roam-directory "~/var/org/")
(setq org-roam-dailies-directory "")
(setq org-roam-dailies-capture-templates
'(("d" "default" entry
#'org-roam-capture--get-point
"* %?"
:file-name "%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n")))
/
Org-fc
Trying a new space-repetition framework.
(setq org-fc-directories "~/var/org/")
;(require 'org-fc-hydra)
Novel
Reading novels in Emacs, how novel!
(require 'justify-kp)
;(setq nov-text-width t)
(setq nov-text-width 100)
(defun my-nov-window-configuration-change-hook ()
(my-nov-post-html-render-hook)
(remove-hook 'window-configuration-change-hook
'my-nov-window-configuration-change-hook
t))
(defun my-nov-post-html-render-hook ()
(if (get-buffer-window)
(let ((max-width (pj-line-width))
buffer-read-only)
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(when (not (looking-at "^[[:space:]]*$"))
(goto-char (line-end-position))
(when (> (shr-pixel-column) max-width)
(goto-char (line-beginning-position))
(pj-justify)))
(forward-line 1))))
(add-hook 'window-configuration-change-hook
'my-nov-window-configuration-change-hook
nil t)))
(add-hook 'nov-post-html-render-hook 'my-nov-post-html-render-hook)
(defun my-nov-font-setup ()
(face-remap-add-relative 'variable-pitch :family "Noto Serif Regular"
:height 1.0
:size 16))
(add-hook 'nov-mode-hook 'my-nov-font-setup)
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
;(add-hook 'nov-mode-hook 'variable-pitch-mode)
Basics
I use notmuch to read and write email from within Emacs.
Here, we'll plan to use w3m to parse HTML email. You do have to make sure w3m is somehow installed on your machine.
(setq mm-text-html-renderer 'w3m)
(setq w3m-fill-column 88)
A few nice to haves for messages. First we kill email message buffers when they are closed. Then we set our auto save directory and generic message directory.
(setq message-kill-buffer-on-exit t)
(setq message-auto-save-directory "~/Mail/colin@unbl.ink/Drafts/")
(setq message-directory "~/Mail/colin@unbl.ink/")
We'll use `msmtp` to send our email. Again, make sure this is installed.
;; sendmail-program "/usr/local/bin/msmtpq" <--- this doesn't work as advertised right now
(setq send-mail-function 'sendmail-send-it
sendmail-program "/usr/local/bin/msmtp"
mail-specify-envelope-from t
message-sendmail-f-is-evil t
message-sendmail-envelope-from 'header
message-sendmail-extra-arguments '("--read-envelope-from")
mail-envelope-from 'header)
Set our custom saved searches so we can conveniently jump to different mail views.
(setq notmuch-saved-searches '((:name "inbox" :query "tag:inbox" :key "i")
(:name "unread" :query "tag:inbox and tag:unread" :key "u")
(:name "jira" :query "tag:jira and date:yesterday..today" :key "j")
(:name "github" :query "tag:github and date:yesterday..today" :key "g")))
We want to make sure notmuch opens in a full window
(after! notmuch
(set-popup-rule! "^\\*notmuch*" :ignore t)
)
Keybindings
(map! :leader
(:prefix "e"
:desc "(s)end queued mail" "s" #'smtpmail-send-queued-mail
:desc "Open (i)nbox" "i" #'=notmuch
:desc "Open (n)otmuch" "n" #'notmuch
:desc "(C)ompose mail" "c" #'notmuch-mua-new-mail))
Eshell
Handful of fun aliases to make working in Eshell almost like a real shell 😄
(after! eshell
(set-eshell-alias!
"djtest" "DJANGO_SETTINGS_MODULE=ff.settings.ci python manage.py test $*"
"djpytest" "DJANGO_SETTINGS_MODULE=ff.settings.ci pytest --reuse-db --black --flake8 --isort --durations=3 $*"
"djsh" "DJANGO_SETTINGS_MODULE=ff.settings.ci python manage.py shell_plus"
"dj" "DJANGO_SETTINGS_MODULE=ff.settings.ci python manage.py $*"
"f" "(other-window 1) && find-file $1"
"l" "ls -lh"
"d" "dired $1"
"gl" "(call-interactively 'magit-log-current)"
"gs" "magit-status"
"gc" "magit-commit"))
Coding
LSP doesn't like big projects and I can't blame it. This forces it to index huge projects without warning us everytime.
(setq lsp-lens-enable 1
lsp-ui-sideline-enable 1
lsp-enable-links 1
lsp-headerline-breadcrumb-enable 1
lsp-modeline-code-actions-enable 1
lsp-modeline-diagnostics-enable 1
lsp-completion-show-detail 1
lsp-file-watch-threshold nil
)
Mastodon
There's gotta be a way to get the token out of password-store for this.
(setq mastodon-instance-url "https://mastodon.technology")
(map! :leader
(:prefix "="
:desc "Open mastodon" "=" #'mastodon
:desc "Update Mastodon timeline" "u" #'mastodon-tl--update
:desc "More Mastodon timeline" "m" #'mastodon-tl--more
:desc "Toot to Mastodon" "t" #'mastodon-toot))
Slack
Configure teams
(use-package! slack
:commands (slack-start)
:init
(setq slack-buffer-emojify t)
(setq slack-prefer-current-team t)
:config
(slack-register-team
:name "15five"
:token (auth-source-pick-first-password
:host "15five.slack.com"
:user "colin.powell@15five.com")
:subscribed-channels '(squad-partnerships water-cooler))
(slack-register-team
:name "RAB"
:token (auth-source-pick-first-password
:host "randomaccessbrewery.slack.com"
:user "colin@onec.me")
:subscribed-channels '(the_taps random))
(evil-define-key 'normal slack-info-mode-map
",u" 'slack-room-update-messages)
(evil-define-key 'normal slack-mode-map
",c" 'slack-buffer-kill
",ra" 'slack-message-add-reaction
",rr" 'slack-message-remove-reaction
",rs" 'slack-message-show-reaction-users
",pl" 'slack-room-pins-list
",pa" 'slack-message-pins-add
",pr" 'slack-message-pins-remove
",mm" 'slack-message-write-another-buffer
",me" 'slack-message-edit
",md" 'slack-message-delete
",u" 'slack-room-update-messages
",2" 'slack-message-embed-mention
",3" 'slack-message-embed-channel
"\C-n" 'slack-buffer-goto-next-message
"\C-p" 'slack-buffer-goto-prev-message)
(evil-define-key 'normal slack-edit-message-mode-map
",k" 'slack-message-cancel-edit
",s" 'slack-message-send-from-buffer
",2" 'slack-message-embed-mention
",3" 'slack-message-embed-channel))
(use-package! alert
:commands (alert)
:init
(setq alert-default-style 'libnotify))
Key bindings
(map! :leader
(:prefix "o"
(:prefix ("s" . "+slack")
:desc "Slack channels" "S" #'slack-start
:desc "Slack channels" "s" #'slack-channel-select
:desc "Slack IMs" "i" #'slack-im-select
:desc "Slack groups" "g" #'slack-group-select
:desc "Slack threads" "t" #'slack-all-threads)))
Beancount
Use Emacs and plain text files for your accounting!
(load! "beancount")
(require 'beancount)
(add-to-list 'auto-mode-alist '("\\.beancount\\'" . beancount-mode))
Pandoc
Here we are trying to auto-translate Word and PDF files to be viewed in Emacs.
(define-derived-mode
pandoc-view-mode
markdown-mode
"pandoc-view-mode"
"View pandoc processing of docx file using markdown mode."
(erase-buffer)
(let* ((pandoc (executable-find "pandoc")))
(insert (shell-command-to-string
(concat pandoc " --wrap=none " (shell-quote-argument (buffer-file-name)) " -t markdown"))))
(not-modified)
(read-only-mode t))
(add-to-list 'auto-mode-alist '("\\.docx\\'" . pandoc-view-mode))
Magit
(after! magit
(magit-wip-after-save-mode t)
(magit-wip-after-apply-mode t)
(setq magit-save-repository-buffers 'dontask
magit-circleci-token "097bf0dd808ad9103d489844e37cecffdf967837"
magit-repository-directories '(("~/src/" . 3)
("~/dotfiles/" . 0))
magit-popup-display-buffer-action nil ;; Not sure why this is here, wonder what it does
magit-display-file-buffer-function #'switch-to-buffer-other-window
magithub-clone-default-directory "~/src" ;; I want my stuff to clone to ~/projects
magithub-preferred-remote-method 'ssh_url)) ;; HTTPS cloning is awful, i authenticate with ssh keys.
; Show gravatars in magit
(setq magit-revision-show-gravatars '("^Author: " . "^Commit: "))
Openwith
(when (require 'openwith nil 'noerror)
(setq openwith-associations
(list
(list (openwith-make-extension-regexp
'("mpg" "mpeg" "mp3" "mp4"
"avi" "wmv" "wav" "mov" "flv"
"ogm" "ogg" "mkv"))
"vlc"
'(file))
(list (openwith-make-extension-regexp
'("pdf" "ps" "ps.gz" "dvi"))
"zathura"
'(file))
))
(openwith-mode 1))
Reveal.js export
(setq org-reveal-root "file:///path-to-reveal.js")
(setq org-reveal-title-slide nil)