Today I re-organized how I use org mode. Historically, the directory was a huge jumble of flatfiles. No subdirectories, no per-file organization. This was great for org-roam's preferred flow, but made Orgzly die a horrible death. That meant the only way I could interact with org-mode was via a computer and I am often not in front of a computer. Putting dailies in a subdirectory, and running my per-day task list out of my index.org file should make it much easier ot keep track fo what needs to get done. The only thing that doesn't really work this way is future events. But that's not such a big part of my flow. I may even be able to get away with just stashing them in the inbox heading (or maybe even a futures heading).
478 lines
16 KiB
Org Mode
478 lines
16 KiB
Org Mode
#+TITLE: DOOM Emacs Configuration
|
||
|
||
This configuration of Emacs is highly opinionated.
|
||
|
||
* Basics
|
||
My identity. Used in a handful of places in Emacs to pre-populate authorship and such.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq user-full-name "Colin Powell"
|
||
user-mail-address "colin@unbl.ink")
|
||
#+END_SRC
|
||
|
||
From a friend at [[https://discord.com/channels/406534637242810369/695450585758957609/715126265715097600][Discord]]
|
||
#+BEGIN_SRC emacs-lisp
|
||
;;(setq ivy-read-action-function #'ivy-hydra-read-action)
|
||
#+END_SRC
|
||
|
||
Sometimes macOS hates us (file descriptors overload)
|
||
#+begin_src emacs-lisp
|
||
(defun file-notify-rm-all-watches ()
|
||
"Remove all existing file notification watches from Emacs."
|
||
(interactive)
|
||
(maphash
|
||
(lambda (key _value)
|
||
(file-notify-rm-watch key))
|
||
file-notify-descriptors))
|
||
|
||
#+end_src
|
||
* 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.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq doom-theme 'doom-one
|
||
doom-font (font-spec :family "FuraCode Nerd Font Mono" :size 12 :weight 'regular)
|
||
doom-big-font (font-spec :family "JetBrains Mono" :size 16 :weight 'regular)
|
||
doom-variable-pitch-font (font-spec :family "Overpass" :size 12))
|
||
#+END_SRC
|
||
** 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.
|
||
#+BEGIN_SRC emacs-lisp
|
||
;; 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))
|
||
#+END_SRC
|
||
** Fringe
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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!
|
||
#+END_SRC
|
||
** Search
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq eww-search-prefix "https://search.unbl.ink/?q=")
|
||
#+END_SRC
|
||
* Keybindings
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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))
|
||
#+END_SRC
|
||
|
||
* Music
|
||
Right now, just make sure I can connect to my local Mopidy server via MPDel.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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))
|
||
#+END_SRC
|
||
* RSS
|
||
Here we’re going to use Miniflux as a back-end store for our RSS feeds.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq elfeed-protocol-fever-maxsize 100)
|
||
(setq elfeed-feeds '(("fever+https://secstate@rss.unbl.ink"
|
||
:api-url "https://rss.unbl.ink/fever/"
|
||
:password "password"
|
||
: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))))
|
||
;;;;; Database auto-save
|
||
|
||
;; Save elfeed db automatically, because if Emacs crashes or is killed (which happens to me
|
||
;; occasionally, especially since I develop packages in a single instance), we'd lose the db
|
||
;; updates not saved.
|
||
(unless (cl-loop for timer in timer-idle-list
|
||
thereis (equal (aref timer 5) #'elfeed-db-save))
|
||
(run-with-idle-timer 400 'repeat #'elfeed-db-save))
|
||
#+END_SRC
|
||
|
||
|
||
Then we’ll setup some nice defaults and font settings for viewing feeds in Elfeed.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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))
|
||
#+END_SRC
|
||
* Formatting
|
||
Here we define the opposite function to fill-paragraph and then map it to Meta-z, which helps when undoing fill paragraph moves.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
#+END_SRC
|
||
|
||
Vale linter, a more up to date proselint!
|
||
|
||
How I went about [[file:/usr/home/powellc/var/org/20200916134500-vale_prose_linter.org::*Installing Vale][installing Vale]]
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
#+END_SRC
|
||
|
||
We love auto-formatting, but not everything should be auto-formatted
|
||
#+begin_src emacs-lisp
|
||
(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
|
||
html-mode
|
||
latex-mode))
|
||
#+end_src
|
||
|
||
* 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.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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/index.org")
|
||
org-ellipsis "…"
|
||
org-image-actual-width '(600)
|
||
org-fontify-quote-and-verse-blocks t
|
||
org-agenda-dim-blocked-tasks nil
|
||
org-pretty-entities t
|
||
org-fancy-priorities-list '("🅰" "🅱" "🅲" "🅳" "🅴")
|
||
org-modules '(ol-eshell
|
||
ol-notmuch
|
||
ob-eval
|
||
ob-exp
|
||
ob-http
|
||
org-id)))
|
||
|
||
;; 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
|
||
#+END_SRC
|
||
|
||
** 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.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
|
||
#+END_SRC
|
||
** Org-roam
|
||
I am absolutely in love with [[https://org-roam.readthedocs.io/en/develop/][Org-roam]]. Everything about it makes taking notes
|
||
easier. I just need to level up with Zettels and web publishing of my notes.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq org-roam-directory "~/var/org/")
|
||
(setq org-roam-dailies-directory "dailies")
|
||
#+END_SRC
|
||
|
||
* Novel
|
||
Reading novels in Emacs, how novel!
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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)
|
||
#+END_SRC
|
||
|
||
* Mail
|
||
** 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.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq mm-text-html-renderer 'w3m)
|
||
(setq w3m-fill-column 88)
|
||
#+END_SRC
|
||
|
||
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.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq message-kill-buffer-on-exit t)
|
||
(setq message-auto-save-directory "~/Mail/colin@unbl.ink/Drafts/")
|
||
(setq message-directory "~/Mail/colin@unbl.ink/")
|
||
#+END_SRC
|
||
|
||
We'll use `msmtp` to send our email. Again, make sure this is installed.
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
;; 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)
|
||
#+END_SRC
|
||
|
||
|
||
Set our custom saved searches so we can conveniently jump to different mail views.
|
||
#+begin_src emacs-lisp
|
||
(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")))
|
||
#+end_src
|
||
|
||
We want to make sure notmuch opens in a full window
|
||
#+begin_src emacs-lisp
|
||
(after! notmuch
|
||
(set-popup-rule! "^\\*notmuch*" :ignore t)
|
||
)
|
||
#+end_src
|
||
** Keybindings
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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))
|
||
#+END_SRC
|
||
|
||
* Eshell
|
||
Handful of fun aliases to make working in Eshell almost like a /real/ shell :smile:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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 $*"
|
||
"ffsh" "python ~/src/github.com/15five/fifteen5/manage.py shell_plus"
|
||
"ffdev" "ssh dev-ff.local "
|
||
"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"))
|
||
#+END_SRC
|
||
* Coding
|
||
|
||
LSP doesn't like big projects and I can't blame it. This forces it to index huge
|
||
projects without warning us every time.
|
||
|
||
#+begin_src emacs-lisp
|
||
(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
|
||
)
|
||
|
||
#+END_SRC
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package lsp-mode
|
||
:commands lsp
|
||
:diminish lsp-mode
|
||
:hook
|
||
(elixir-mode . lsp)
|
||
:init
|
||
(add-to-list 'exec-path "~/.emacs.d/var/elixir-ls"))
|
||
#+end_src
|
||
|
||
* Mastodon
|
||
There's gotta be a way to get the token out of password-store for this.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setq mastodon-instance-url "https://fosstodon.org"
|
||
mastodon-active-user "colin@unbl.ink")
|
||
|
||
(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))
|
||
#+END_SRC
|
||
* Beancount
|
||
Use Emacs and plain text files for your accounting!
|
||
|
||
#+BEGIN_SRC emacs-lisp
|
||
(load! "beancount")
|
||
(require 'beancount)
|
||
(add-to-list 'auto-mode-alist '("\\.beancount\\'" . beancount-mode))
|
||
#+END_SRC
|
||
* Pandoc
|
||
Here we are trying to auto-translate Word and PDF files to be viewed in Emacs.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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))
|
||
|
||
#+END_SRC
|
||
* Magit
|
||
#+BEGIN_SRC emacs-lisp
|
||
(after! magit
|
||
(magit-wip-after-save-mode t)
|
||
(magit-wip-after-apply-mode t)
|
||
|
||
(setq magit-save-repository-buffers 'dontask
|
||
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: "))
|
||
|
||
#+END_SRC
|
||
* Openwith
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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))
|
||
#+END_SRC
|
||
* Reveal.js export
|
||
#+begin_src emacs-lisp
|
||
(setq org-reveal-root "file:///path-to-reveal.js")
|
||
(setq org-reveal-title-slide nil)
|
||
#+end_src
|