#+TITLE: DOOM Emacs Configuration

This configuration of Emacs is highly opinionated.

* Basics
I don't actually know what this does ... :thinking:
#+BEGIN_SRC emacs-lisp
(add-to-list 'default-frame-alist '(inhibit-double-buffering . t))
#+END_SRC

My identity. Used in a handful of places in Emacs to prepopulate authorship and such.
#+BEGIN_SRC emacs-lisp
(setq user-full-name "Colin Powell"
      user-mail-address "colin@unbl.ink")
#+END_SRC

** TODO Figure out what `inhibit-double-buffering` does in Emacs
* 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.

I love [[https://blog.golang.org/go-fonts][Go Mono]]. But sometimes Emacs is sensitive 
to fonts and performance suffers, so we have Fira Mono to fall back on.

#+BEGIN_SRC emacs-lisp
(setq doom-font (font-spec :family "FuraCode Nerd Font Mono" :size 12)
          doom-big-font (font-spec :family "FuraCode Nerd Font Mono" :size 17)
          doom-variable-pitch-font (font-spec :family "Noto Serif" :size 12)
          doom-serif-font (font-spec :family "FuraCode Nerd Font Mono")
          doom-theme 'srcery)
#+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 30) ; 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 . 30))
;; Dash highlighting
(after! dash (dash-enable-font-lock))
#+END_SRC
** Dash
I need to figure out what this does, hence the task below.
#+BEGIN_SRC emacs-lisp
;; Dash highlighting
(after! dash (dash-enable-font-lock))
#+END_SRC
*** TODO Figure out what dash highlighting is all about
* 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 "t"
        :desc "Switch themes"           "t" #'load-theme)
      (:prefix "o"
        :desc "Elfeed feed reader"      "f" #'elfeed
        :desc "Hckrnews"                "h" #'hackernews
        :desc "Lobste.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 "Search project TODOs"    "t" #'+ivy/tasks
        :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
* 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.

#+BEGIN_SRC emacs-lisp
(setq wttrin-default-cities '("Castine, ME" "San Francisco" "Thessaloniki"))
(setq wttrin-default-accept-language '("Accept-Language" . "en-US"))

(map!
 (:leader
   (:prefix "o"
    :desc "Weather" "w" #'wttrin)))
#+END_SRC
* Search
#+BEGIN_SRC emacs-lisp
;; app/search
(after! web-search
  (push '("Searx" "http://search.unbl.ink/?q=%s")
        web-search-providers)
  (setq web-search-default-provider "Searx"))
#+END_SRC
* Eglot
Eglot versus LSP? Until 27 lands stable, Eglot wins. I can actually read all
it's source.
    
#+BEGIN_SRC emacs-lisp
;; app/eglot
;; Back to eglot ...
(add-hook 'foo-mode-hook 'eglot-ensure)

#+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 (elfeed)
** Basic configuration
#+BEGIN_SRC emacs-lisp
(add-hook! 'elfeed-show-mode-hook (text-scale-set 1.5))

(setq rmh-elfeed-org-files (list "~/org/elfeed.org"))
(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)))

; crontab-like elfeed updating
(run-with-timer 0 (* 30 60) 'elfeed-update)

(setq httpd-host "0.0.0.0")
(setq httpd-port 10000)
#+END_SRC
** Key bindings
#+BEGIN_SRC emacs-lisp
(map! :leader
      (:prefix "r" ; For (m)essaging
        :desc "Open Elfeed"   "o" #'elfeed
        :desc "Update Elfeed"   "u" #'elfeed-update))
#+END_SRC
* Niceties
#+BEGIN_SRC emacs-lisp
(add-hook 'prog-mode-hook #'goto-address-mode) ;; Linkify links!

;;; It is the opposite of fill-paragraph
(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)))

;; Handy key definition
(define-key global-map "\M-z" 'unfill-paragraph)
#+END_SRC

Helpful modes to make working less painful.
#+BEGIN_SRC emacs-lisp
;; A rainbow cat, why not?
(nyan-mode)

;; Show the cursor when we make jumps, I'm too old for this shit.
(beacon-mode)

#+END_SRC

#+BEGIN_SRC emacs-lisp
;; Emojis, fuck me.
(add-hook 'after-init-hook #'global-emojify-mode)

;; PlantUML is awesome for quick diagrams
(add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode))

;; 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")))

#+END_SRC

#+BEGIN_SRC emacs-lisp
(setq +workspaces-switch-project-function #'ignore
      +format-on-save-enabled-modes '(python-mode)
      +pretty-code-enabled-modes '(emacs-lisp-mode org-mode))
#+END_SRC
* Org-mode
** Basic configuration

Do I use deft? Not really. Every once in a while I think I will. This makes sure
it works when I get an impulse.
#+BEGIN_SRC emacs-lisp
(setq deft-extensions '("org"))
(setq deft-directory "~/org")
#+END_SRC

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)
(add-hook 'org-mode-hook 'typo-mode)
; For org mode serif
(add-hook 'org-mode-hook 'variable-pitch-mode)
(add-hook 'org-mode-hook
            '(lambda ()
               (mapc
                (lambda (face) ;; Other fonts with fixed-pitch.
                  (set-face-attribute face nil :inherit 'fixed-pitch))
                (list 'org-link
                      'org-block
                      'org-table
                      'org-block-begin-line
                      'org-block-end-line))))

(add-hook 'org-mode-hook #'auto-fill-mode)
(remove-hook 'org-mode-hook #'word-wrap-mode)
#+END_SRC

#+BEGIN_SRC emacs-lisp
(after! org
  (setq org-directory (expand-file-name "~/org/")
        org-agenda-files (list org-directory)
        org-agenda-window-setup 'only-window
        org-pretty-entities t
        org-agenda-dim-blocked-tasks nil
        org-log-done 'time
        org-fontify-whole-heading-line t
        org-fontify-done-headline t
        org-fontify-quote-and-verse-blocks t
        org-ellipsis "…"
        org-capture-templates
            '(("i" "Send to inbox" entry (file+headline "~/org/inbox.org" "Inbox")
               "* TODO %?\n"))
        org-todo-keywords
            '((sequence "TODO(t)" "NEXT(n)" "MAYBE(m)" "|" "DONE(d)" "WONTDO(w)"))
        org-tag-alist '(("@personal" . ?h)
                        ("@farm" . ?f)
                        ("@town" . ?s)
                        ("@errand" . ?e)
                        ("@15five" . ?w)
                        ("@family" . ?m))
        org-modules '(ol-eshell
                      ol-notmuch
                      ob-eval
                      ob-exp
                      ob-http
                      org-drill
                      org-id)))

;; Don’t display git gutter in org mode
(after! git-gutter
  (setq git-gutter:disabled-modes '(org-mode image-mode)))

;; Refiling
(setq org-refile-targets '((nil :maxlevel . 9)
                                (org-agenda-files :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
#+BEGIN_SRC emacs-lisp
;; org-set-tags-command
(setq +inbox-file "~/org/inbox.org")
(defun +open-inbox-file ()
  (interactive)
  "Opens the inbox file"
  (find-file +inbox-file))

(map!
 :leader
   :desc "Open inbox" "I" #'+open-inbox-file
   :desc "Open today" "T" #'org-roam-today
   :desc "Open tomorrow" "N" #'org-roam-tomorrow)

(map! :leader
      (:prefix "f" :desc "Save all org buffers" "a" #'org-save-all-org-buffers))
#+END_SRC
** Agenda configuration
#+BEGIN_SRC emacs-lisp
(setq org-agenda-span 5
      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))
#+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
(use-package! org-roam
  :commands (org-roam-insert org-roam-find-file org-roam)
  :init
  (setq org-roam-directory "~/org/")
  (map! :leader
        :prefix "n"
        :desc "Org-Roam-Insert" "i" #'org-roam-insert
        :desc "Org-Roam-Find"   "/" #'org-roam-find-file
        :desc "Org-Roam-Buffer" "r" #'org-roam)
  :config
  (org-roam-mode +1))

(with-eval-after-load 'org-roam
  (with-eval-after-load 'company
    (with-eval-after-load 'org
      (require 'company-org-roam)
      (company-org-roam-init))))
#+END_SRC

* Nov.el
Reading novels in Emacs, how novel!

#+BEGIN_SRC emacs-lisp
(require 'justify-kp)
;(setq nov-text-width t)
(setq nov-text-width 80)

(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)
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))

(defun my-nov-font-setup ()
  (face-remap-add-relative 'variable-pitch :family "Liberation Serif"
                                           :size 20
                                           :height 1.0))
(add-hook 'nov-mode-hook 'my-nov-font-setup)
#+END_SRC

* Mail
I use notmuch to read and write email from within Emacs.

#+BEGIN_SRC emacs-lisp
(load! "+mail")    ;; Mail stuff

;; Use word wrap in text emails, because most people suck
(remove-hook 'text-mode-hook #'auto-fill-mode)
(add-hook 'message-mode-hook #'word-wrap-mode)
#+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!
   "f"     "(other-window 1) && find-file $1"
   "l"     "ls -lh"
   "d"     "dired $1"
   "dc"    "docker-compose $*"
   "fftest"  "cd ~/src/ff/fifteen5 && docker-compose exec app django-admin test -k $*"
   "ffsh"  "cd ~/src/ff/fifteen5/ && docker-compose exec app django-admin shell_plus"
   "ffdj"  "cd ~/src/ff/fifteen5/ && docker-compose exec app django-admin $*"
   "gl"    "(call-interactively 'magit-log-current)"
   "gs"    "magit-status"
   "gc"    "magit-commit"))
#+END_SRC
* Ranger
Trick out ranger a bit.

#+BEGIN_SRC emacs-lisp
(use-package! ranger
  :commands (ranger deer ranger-override-dired-fn)
  :config
  (set-popup-rule! "^\\*ranger" :ignore t))

(map!
 (:leader
   (:prefix "a"
    :desc "Ranger" "r" #'ranger
    :desc "Deer" "d" #'deer)))

(add-hook! dired-mode #'ranger-override-dired-fn) ;; Override dired-mode so it uses deer
#+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://mastodon.technology")

(map! :leader
      (:prefix "="
        :desc "Open mastodon"                "=" #'mastodon
        :desc "Update Mastodon timeline"     "u" #'mastodon-tl--update
        :desc "Toot to Mastodon"             "t" #'mastodon-toot))
#+END_SRC
* Slack
** Configure teams
#+BEGIN_SRC emacs-lisp
(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-admin 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))
#+END_SRC
** Key bindings
#+BEGIN_SRC emacs-lisp
(map! :leader
      (:prefix "y" ; For (y)acking
        :desc "Slack channels"   "s" #'slack-start
        :desc "Slack channels"   "c" #'slack-channel-select
        :desc "Slack IMs"        "i" #'slack-im-select
        :desc "Slack groups"     "g" #'slack-group-select
        :desc "Slack threads"    "t" #'slack-all-threads))
#+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
