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-tokyo-night
|
||
doom-font (font-spec :family "Iosevka" :size 12 :weight 'regular)
|
||
doom-big-font (font-spec :family "Iosevka" :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 (append '("~/var/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
|