/home/daviwil/.dotfiles
The path to GNUrvana

Emacs Configuration

This is an ongoing evolution of my original Emacs configuration files, inspired by a bunch of resources I've found online.

Table of Contents

Startup Performance

Make startup faster by reducing the frequency of garbage collection and then use a hook to measure Emacs startup time.


;; The default is 800 kilobytes.  Measured in bytes.
(setq gc-cons-threshold (* 50 1000 1000))

;; Profile emacs startup
(add-hook 'emacs-startup-hook
          (lambda ()
            (message "*** Emacs loaded in %s with %d garbage collections."
                     (format "%.2f seconds"
                             (float-time
                              (time-subtract after-init-time before-init-time)))
                     gcs-done)))

Native Compilation

I've started experimenting with the native-comp branch of Emacs for increased performance. Here are some settings to tweak the behavior slightly:


;; Silence compiler warnings as they can be pretty disruptive
(setq comp-async-report-warnings-errors nil)

System Settings

Some parts of the configuration require knowledge of whether Emacs is running on a Guix system or in Termux on Android. Also load system-specific settings from per-system-settings.el.


(load-file "~/.dotfiles/.emacs.d/lisp/dw-settings.el")

;; Load settings for the first time
(dw/load-system-settings)

(require 'subr-x)
(setq dw/is-termux
      (string-suffix-p "Android" (string-trim (shell-command-to-string "uname -a"))))

(setq dw/is-guix-system (and (eq system-type 'gnu/linux)
                             (require 'f)
                             (string-equal (f-read "/etc/issue")
                                           "\nThis is the GNU system.  Welcome.\n")))

Package Management

Set up ELPA, MELPA, and Org package repositories and load use-package to manage package configuration.

NOTE: I'm keeping this section in but disabling tangling for now because I'm trying out straight.el as an alternative.


;; Initialize package sources
(require 'package)

(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("melpa-stable" . "https://stable.melpa.org/packages/")
                         ("org" . "https://orgmode.org/elpa/")
                         ("elpa" . "https://elpa.gnu.org/packages/")))

;; Fix an issue accessing the ELPA archive in Termux
(when dw/is-termux
  (setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3"))

(package-initialize)
;; (unless package-archive-contents
;;   (package-refresh-contents))

;; Initialize use-package on non-Linux platforms
(unless (or (package-installed-p 'use-package)
            dw/is-guix-system)
   (package-install 'use-package))
(require 'use-package)

;; Uncomment this to get a reading on packages that get loaded at startup
;;(setq use-package-verbose t)

;; On non-Guix systems, "ensure" packages by default
(setq use-package-always-ensure (not dw/is-guix-system))

Guix Packages


"emacs-use-package"

straight.el

Trying out straight.el for package management. So far so good!


;; Bootstrap straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
      (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
        "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
        'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

;; Always use straight to install on systems other than Linux
(setq straight-use-package-by-default (not (eq system-type 'gnu/linux)))

;; Use straight.el for use-package expressions
(straight-use-package 'use-package)

;; Load the helper package for commands like `straight-x-clean-unused-repos'
(require 'straight-x)

Keep .emacs.d Clean

I don't want a bunch of transient files showing up as untracked in the Git repo so I move them all to another location.


;; Change the user-emacs-directory to keep unwanted things out of ~/.emacs.d
(setq user-emacs-directory (expand-file-name "~/.cache/emacs/")
      url-history-file (expand-file-name "url/history" user-emacs-directory))

;; Use no-littering to automatically set common paths to the new user-emacs-directory
(use-package no-littering)

;; Keep customization settings in a temporary file (thanks Ambrevar!)
(setq custom-file
      (if (boundp 'server-socket-dir)
          (expand-file-name "custom.el" server-socket-dir)
        (expand-file-name (format "emacs-custom-%s.el" (user-uid)) temporary-file-directory)))
(load custom-file t)

Guix Packages


"emacs-no-littering"

Update Load Path

I've got a folder of custom Emacs Lisp libraries which must be added to the load path.


;; Add my library path to load-path
(push "~/.dotfiles/.emacs.d/lisp" load-path)

Default Coding System

Avoid constant errors on Windows about the coding system by setting the default to UTF-8.


(set-default-coding-systems 'utf-8)

Server Mode

Start the Emacs server from this instance so that all emacsclient calls are routed here.


(server-start)

Desktop Environment

Load up the desktop environment if on a machine that supports it and the --use-exwm argument was passed to Emacs on startup. Desktop environment and window management code can be found in Desktop.org.


(setq dw/exwm-enabled (and (not dw/is-termux)
                           (eq window-system 'x)
                           (seq-contains command-line-args "--use-exwm")))

(when dw/exwm-enabled
  (require 'dw-desktop))

Guix Packages

Despite the code being in Desktop.org, include the emacs packages for the desktop environment in this file so that they can be included in the Emacs profile.


"emacs-exwm"
"emacs-desktop-environment"

Keyboard Bindings

ESC Cancels All


(global-set-key (kbd "<escape>") 'keyboard-escape-quit)

Rebind C-u

Since I let evil-mode take over C-u for buffer scrolling, I need to re-bind the universal-argument command to another key sequence. I'm choosing C-M-u for this purpose.


(global-set-key (kbd "C-M-u") 'universal-argument)

Let's Be Evil

Some tips can be found here:


(defun dw/evil-hook ()
  (dolist (mode '(custom-mode
                  eshell-mode
                  git-rebase-mode
                  erc-mode
                  circe-server-mode
                  circe-chat-mode
                  circe-query-mode
                  sauron-mode
                  term-mode))
  (add-to-list 'evil-emacs-state-modes mode)))

(defun dw/dont-arrow-me-bro ()
  (interactive)
  (message "Arrow keys are bad, you know?"))

(use-package undo-tree
  :init
  (global-undo-tree-mode 1))

(use-package evil
  :init
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)
  (setq evil-want-C-u-scroll t)
  (setq evil-want-C-i-jump nil)
  (setq evil-respect-visual-line-mode t)
  (setq evil-undo-system 'undo-tree)
  :config
  (add-hook 'evil-mode-hook 'dw/evil-hook)
  (evil-mode 1)
  (define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state)
  (define-key evil-insert-state-map (kbd "C-h") 'evil-delete-backward-char-and-join)

  ;; Use visual line motions even outside of visual-line-mode buffers
  (evil-global-set-key 'motion "j" 'evil-next-visual-line)
  (evil-global-set-key 'motion "k" 'evil-previous-visual-line)

  (unless dw/is-termux
    ;; Disable arrow keys in normal and visual modes
    (define-key evil-normal-state-map (kbd "<left>") 'dw/dont-arrow-me-bro)
    (define-key evil-normal-state-map (kbd "<right>") 'dw/dont-arrow-me-bro)
    (define-key evil-normal-state-map (kbd "<down>") 'dw/dont-arrow-me-bro)
    (define-key evil-normal-state-map (kbd "<up>") 'dw/dont-arrow-me-bro)
    (evil-global-set-key 'motion (kbd "<left>") 'dw/dont-arrow-me-bro)
    (evil-global-set-key 'motion (kbd "<right>") 'dw/dont-arrow-me-bro)
    (evil-global-set-key 'motion (kbd "<down>") 'dw/dont-arrow-me-bro)
    (evil-global-set-key 'motion (kbd "<up>") 'dw/dont-arrow-me-bro))

  (evil-set-initial-state 'messages-buffer-mode 'normal)
  (evil-set-initial-state 'dashboard-mode 'normal))

(use-package evil-collection
  :after evil
  :init
  (setq evil-collection-company-use-tng nil)  ;; Is this a bug in evil-collection?
  :custom
  (evil-collection-outline-bind-tab-p nil)
  :config
  (setq evil-collection-mode-list
        (remove 'lispy evil-collection-mode-list))
  (evil-collection-init))

Guix Packages


"emacs-evil"
"emacs-evil-collection"
"emacs-undo-tree"

Keybinding Panel (which-key)

which-key is great for getting an overview of what keybindings are available based on the prefix keys you entered. Learned about this one from Spacemacs.


(use-package which-key
  :init (which-key-mode)
  :diminish which-key-mode
  :config
  (setq which-key-idle-delay 0.3))

Guix Packages


"emacs-which-key"

Simplify Leader Bindings (general.el)

general.el is a fantastic library for defining prefixed keybindings, especially in conjunction with Evil modes.


(use-package general
  :config
  (general-evil-setup t)

  (general-create-definer dw/leader-key-def
    :keymaps '(normal insert visual emacs)
    :prefix "SPC"
    :global-prefix "C-SPC")

  (general-create-definer dw/ctrl-c-keys
    :prefix "C-c"))

Guix Packages


"emacs-general"

Enable keychord bind with use-package


(use-package use-package-chords
  :disabled
  :config (key-chord-mode 1))

General Configuration

User Interface

Clean up Emacs' user interface, make it more minimal.


;; Thanks, but no thanks
(setq inhibit-startup-message t)

(unless dw/is-termux
  (scroll-bar-mode -1)        ; Disable visible scrollbar
  (tool-bar-mode -1)          ; Disable the toolbar
  (tooltip-mode -1)           ; Disable tooltips
  (set-fringe-mode 10))       ; Give some breathing room

(menu-bar-mode -1)            ; Disable the menu bar

;; Set up the visible bell
(setq visible-bell t)

Improve scrolling.


(unless dw/is-termux
  (setq mouse-wheel-scroll-amount '(1 ((shift) . 1))) ;; one line at a time
  (setq mouse-wheel-progressive-speed nil) ;; don't accelerate scrolling
  (setq mouse-wheel-follow-mouse 't) ;; scroll window under mouse
  (setq scroll-step 1) ;; keyboard scroll one line at a time
  (setq use-dialog-box nil)) ;; Disable dialog boxes since they weren't working in Mac OSX

Set frame transparency and maximize windows by default.


(unless dw/is-termux
  (set-frame-parameter (selected-frame) 'alpha '(90 . 90))
  (add-to-list 'default-frame-alist '(alpha . (90 . 90)))
  (set-frame-parameter (selected-frame) 'fullscreen 'maximized)
  (add-to-list 'default-frame-alist '(fullscreen . maximized)))

Enable line numbers and customize their format.


(column-number-mode)

;; Enable line numbers for some modes
(dolist (mode '(text-mode-hook
                prog-mode-hook
                conf-mode-hook))
  (add-hook mode (lambda () (display-line-numbers-mode 1))))

;; Override some modes which derive from the above
(dolist (mode '(org-mode-hook))
  (add-hook mode (lambda () (display-line-numbers-mode 0))))

Don't warn for large files (shows up when launching videos)


(setq large-file-warning-threshold nil)

Don't warn for following symlinked files


(setq vc-follow-symlinks t)

Don't warn when advice is added for functions


(setq ad-redefinition-action 'accept)

Theme

These days I bounce around between themes included with DOOM Themes since they're well-designed and integrate with a lot of Emacs packages.

A nice gallery of Emacs themes can be found at https://emacsthemes.com/.

Alternate themes:

  • doom-snazzy
  • doom-vibrant

(use-package spacegray-theme :defer t)
(use-package doom-themes :defer t)
(unless dw/is-termux
  (load-theme 'doom-palenight t)
  (doom-themes-visual-bell-config))

Guix Packages


"emacs-doom-themes"
"emacs-spacegray-theme"

Font

Set the font

Different platforms need different default font sizes, and Fira Mono is currently my favorite face.


;; Set the font face based on platform
(pcase system-type
  ((or 'gnu/linux 'windows-nt 'cygwin)
   (set-face-attribute 'default nil
                       :font "JetBrains Mono"
                       :weight 'light
                       :height (dw/system-settings-get 'emacs/default-face-size)))
  ('darwin (set-face-attribute 'default nil :font "Fira Mono" :height 170)))

;; Set the fixed pitch face
(set-face-attribute 'fixed-pitch nil
                    :font "JetBrains Mono"
                    :weight 'light
                    :height (dw/system-settings-get 'emacs/fixed-face-size))

;; Set the variable pitch face
(set-face-attribute 'variable-pitch nil
                    ;; :font "Cantarell"
                    :font "Iosevka Aile"
                    :height (dw/system-settings-get 'emacs/variable-face-size)
                    :weight 'light)

Enable proper Unicode glyph support


(defun dw/replace-unicode-font-mapping (block-name old-font new-font)
  (let* ((block-idx (cl-position-if
                         (lambda (i) (string-equal (car i) block-name))
                         unicode-fonts-block-font-mapping))
         (block-fonts (cadr (nth block-idx unicode-fonts-block-font-mapping)))
         (updated-block (cl-substitute new-font old-font block-fonts :test 'string-equal)))
    (setf (cdr (nth block-idx unicode-fonts-block-font-mapping))
          `(,updated-block))))

(use-package unicode-fonts
  :disabled
  :if (not dw/is-termux)
  :custom
  (unicode-fonts-skip-font-groups '(low-quality-glyphs))
  :config
  ;; Fix the font mappings to use the right emoji font
  (mapcar
    (lambda (block-name)
      (dw/replace-unicode-font-mapping block-name "Apple Color Emoji" "Noto Color Emoji"))
    '("Dingbats"
      "Emoticons"
      "Miscellaneous Symbols and Pictographs"
      "Transport and Map Symbols"))
  (unicode-fonts-setup))

Guix Packages


;; "emacs-unicode-fonts"

Emojis in buffers


(use-package emojify
  :hook (erc-mode . emojify-mode)
  :commands emojify-mode)

Guix Packages


"emacs-emojify"

Mode Line

Basic Customization


(setq display-time-format "%l:%M %p %b %y"
      display-time-default-load-average nil)

Enable Mode Diminishing

The diminish package hides pesky minor modes from the modelines.


(use-package diminish)

Smart Mode Line

Prettify the modeline with smart-mode-line. Really need to re-evaluate the ordering of mode-line-format. Also not sure if rm-excluded-modes is needed anymore if I set up diminish correctly.


(use-package smart-mode-line
  :disabled
  :if dw/is-termux
  :config
  (setq sml/no-confirm-load-theme t)
  (sml/setup)
  (sml/apply-theme 'respectful)  ; Respect the theme colors
  (setq sml/mode-width 'right
      sml/name-width 60)

  (setq-default mode-line-format
  `("%e"
      ,(when dw/exwm-enabled
          '(:eval (format "[%d] " exwm-workspace-current-index)))
      mode-line-front-space
      evil-mode-line-tag
      mode-line-mule-info
      mode-line-client
      mode-line-modified
      mode-line-remote
      mode-line-frame-identification
      mode-line-buffer-identification
      sml/pos-id-separator
      (vc-mode vc-mode)
      " "
      ;mode-line-position
      sml/pre-modes-separator
      mode-line-modes
      " "
      mode-line-misc-info))

  (setq rm-excluded-modes
    (mapconcat
      'identity
      ; These names must start with a space!
      '(" GitGutter" " MRev" " company"
      " Helm" " Undo-Tree" " Projectile.*" " Z" " Ind"
      " Org-Agenda.*" " ElDoc" " SP/s" " cider.*")
      "\\|")))

Guix Packages


"emacs-smart-mode-line"

Doom Modeline


;; You must run (all-the-icons-install-fonts) one time after
;; installing this package!

(use-package minions
  :hook (doom-modeline-mode . minions-mode))

(use-package doom-modeline
  :after eshell     ;; Make sure it gets hooked after eshell
  :hook (after-init . doom-modeline-init)
  :custom-face
  (mode-line ((t (:height 0.85))))
  (mode-line-inactive ((t (:height 0.85))))
  :custom
  (doom-modeline-height 15)
  (doom-modeline-bar-width 6)
  (doom-modeline-lsp t)
  (doom-modeline-github nil)
  (doom-modeline-mu4e nil)
  (doom-modeline-irc nil)
  (doom-modeline-minor-modes t)
  (doom-modeline-persp-name nil)
  (doom-modeline-buffer-file-name-style 'truncate-except-project)
  (doom-modeline-major-mode-icon nil))

Guix Packages


"emacs-doom-modeline"
"emacs-all-the-icons"
"emacs-minions"

Workspaces


(use-package perspective
  :demand t
  :bind (("C-M-k" . persp-switch)
         ("C-M-n" . persp-next)
         ("C-x k" . persp-kill-buffer*))
  :custom
  (persp-initial-frame-name "Main")
  :config
  ;; Running `persp-mode' multiple times resets the perspective list...
  (unless (equal persp-mode t)
    (persp-mode)))

Guix Packages


"emacs-perspective"

Notifications

alert is a great library for showing notifications from other packages in a variety of ways. For now I just use it to surface desktop notifications from package code.


(use-package alert
  :commands alert
  :config
  (setq alert-default-style 'notifications))

Guix Packages


"emacs-alert"

Auto-Saving Changed Files


(use-package super-save
  :defer 1
  :diminish super-save-mode
  :config
  (super-save-mode +1)
  (setq super-save-auto-save-when-idle t))

Guix Packages


"emacs-super-save"

Auto-Reverting Changed Files


;; Revert Dired and other buffers
(setq global-auto-revert-non-file-buffers t)

;; Revert buffers when the underlying file has changed
(global-auto-revert-mode 1)

UI Toggles


(dw/leader-key-def
  "t"  '(:ignore t :which-key "toggles")
  "tw" 'whitespace-mode
  "tt" '(counsel-load-theme :which-key "choose theme"))

Highlight Matching Braces


(use-package paren
  :config
  (set-face-attribute 'show-paren-match-expression nil :background "#363e4a")
  (show-paren-mode 1))

Displaying World Time

display-time-world command provides a nice display of the time at a specified list of timezones. Nice for working in a team with remote members.


(setq display-time-world-list
  '(("Etc/UTC" "UTC")
    ("America/Los_Angeles" "Seattle")
    ("America/New_York" "New York")
    ("Europe/Athens" "Athens")
    ("Pacific/Auckland" "Auckland")
    ("Asia/Shanghai" "Shanghai")))
(setq display-time-world-time-format "%a, %d %b %I:%M %p %Z")

Pinentry

Emacs can be prompted for the PIN of GPG private keys, we just need to set epa-pinentry-mode to accomplish that:


(unless (or dw/is-termux
            (eq system-type 'windows-nt))
  (setq epa-pinentry-mode 'loopback)
  (pinentry-start))

Guix Packages


"emacs-pinentry"
"pinentry-emacs"

TRAMP


;; Set default connection mode to SSH
(setq tramp-default-method "ssh")

Emacs as External Editor


(defun dw/show-server-edit-buffer (buffer)
  ;; TODO: Set a transient keymap to close with 'C-c C-c'
  (split-window-vertically -15)
  (other-window 1)
  (set-buffer buffer))

(setq server-window #'dw/show-server-edit-buffer)

Editing Configuration

Tab Widths

Default to an indentation size of 2 spaces since it's the norm for pretty much every language I use.


(setq-default tab-width 2)
(setq-default evil-shift-width tab-width)

Use spaces instead of tabs for indentation


(setq-default indent-tabs-mode nil)

Commenting Lines


(use-package evil-nerd-commenter
  :bind ("M-/" . evilnc-comment-or-uncomment-lines))

Guix Packages


"emacs-evil-nerd-commenter"

Automatically clean whitespace


(use-package ws-butler
  :hook ((text-mode . ws-butler-mode)
         (prog-mode . ws-butler-mode)))

Guix Packages


"emacs-ws-butler"

Use Parinfer for Lispy languages


(use-package parinfer
  :disabled
  :hook ((clojure-mode . parinfer-mode)
         (emacs-lisp-mode . parinfer-mode)
         (common-lisp-mode . parinfer-mode)
         (scheme-mode . parinfer-mode)
         (lisp-mode . parinfer-mode))
  :config
  (setq parinfer-extensions
      '(defaults       ; should be included.
        pretty-parens  ; different paren styles for different modes.
        evil           ; If you use Evil.
        smart-tab      ; C-b & C-f jump positions and smart shift with tab & S-tab.
        smart-yank)))  ; Yank behavior depend on mode.

(dw/leader-key-def
  "tp" 'parinfer-toggle-mode)

Guix Packages


"emacs-parinfer-mode"

Origami.el for Folding


(use-package origami
  :hook (yaml-mode . origami-mode))

Guix Packages


"emacs-origami-el"

Configuration Files

Configuration file management with dotcrafter.el

I've been working on a package called dotcrafter.el for complete management of your dotfiles folder with special emphasis on configurations written with Org Mode.

Check out my Learning Emacs Lisp series to see this package be written from scratch!


(use-package dotcrafter
  :straight '(dotcrafter :host github
                          :repo "daviwil/dotcrafter.el"
                          :branch "main")
  :custom
  (dotcrafter-org-files '("Emacs.org"
                          "Desktop.org"
                          "Systems.org"
                          "Mail.org"
                          "Workflow.org"))
  :init
  (require 'dotcrafter) ; Not sure why I have to do this...
  :config
  (dotcrafter-mode))

Helpers


(defun dw/org-file-jump-to-heading (org-file heading-title)
  (interactive)
  (find-file (expand-file-name org-file))
  (goto-char (point-min))
  (search-forward (concat "* " heading-title))
  (org-overview)
  (org-reveal)
  (org-show-subtree)
  (forward-line))

(defun dw/org-file-show-headings (org-file)
  (interactive)
  (find-file (expand-file-name org-file))
  (counsel-org-goto)
  (org-overview)
  (org-reveal)
  (org-show-subtree)
  (forward-line))

Bindings


(dw/leader-key-def
  "fn" '((lambda () (interactive) (counsel-find-file "~/Notes/")) :which-key "notes")
  "fd"  '(:ignore t :which-key "dotfiles")
  "fdd" '((lambda () (interactive) (find-file "~/.dotfiles/Desktop.org")) :which-key "desktop")
  "fde" '((lambda () (interactive) (find-file (expand-file-name "~/.dotfiles/Emacs.org"))) :which-key "edit config")
  "fdE" '((lambda () (interactive) (dw/org-file-show-headings "~/.dotfiles/Emacs.org")) :which-key "edit config")
  "fdm" '((lambda () (interactive) (find-file "~/.dotfiles/Mail.org")) :which-key "mail")
  "fdM" '((lambda () (interactive) (counsel-find-file "~/.dotfiles/.config/guix/manifests/")) :which-key "manifests")
  "fds" '((lambda () (interactive) (dw/org-file-jump-to-heading "~/.dotfiles/Systems.org" "Base Configuration")) :which-key "base system")
  "fdS" '((lambda () (interactive) (dw/org-file-jump-to-heading "~/.dotfiles/Systems.org" system-name)) :which-key "this system")
  "fdp" '((lambda () (interactive) (dw/org-file-jump-to-heading "~/.dotfiles/Desktop.org" "Panel via Polybar")) :which-key "polybar")
  "fdw" '((lambda () (interactive) (find-file (expand-file-name "~/.dotfiles/Workflow.org"))) :which-key "workflow")
  "fdv" '((lambda () (interactive) (find-file "~/.dotfiles/.config/vimb/config")) :which-key "vimb"))

Stateful Keymaps with Hydra


(use-package hydra
  :defer 1)

Guix Packages


"emacs-hydra"

Better Completions with Ivy

I currently use Ivy, Counsel, and Swiper to navigate around files, buffers, and projects super quickly. Here are some workflow notes on how to best use Ivy:

  • While in an Ivy minibuffer, you can search within the current results by using S-Space.
  • To quickly jump to an item in the minibuffer, use C-' to get Avy line jump keys.
  • To see actions for the selected minibuffer item, use M-o and then press the action's key.
  • Super useful: Use C-c C-o to open ivy-occur to open the search results in a separate buffer. From there you can click any item to perform the ivy action.

(use-package ivy
  :diminish
  :bind (("C-s" . swiper)
         :map ivy-minibuffer-map
         ("TAB" . ivy-alt-done)
         ("C-f" . ivy-alt-done)
         ("C-l" . ivy-alt-done)
         ("C-j" . ivy-next-line)
         ("C-k" . ivy-previous-line)
         :map ivy-switch-buffer-map
         ("C-k" . ivy-previous-line)
         ("C-l" . ivy-done)
         ("C-d" . ivy-switch-buffer-kill)
         :map ivy-reverse-i-search-map
         ("C-k" . ivy-previous-line)
         ("C-d" . ivy-reverse-i-search-kill))
  :init
  (ivy-mode 1)
  :config
  (setq ivy-use-virtual-buffers t)
  (setq ivy-wrap t)
  (setq ivy-count-format "(%d/%d) ")
  (setq enable-recursive-minibuffers t)

  ;; Use different regex strategies per completion command
  (push '(completion-at-point . ivy--regex-fuzzy) ivy-re-builders-alist) ;; This doesn't seem to work...
  (push '(swiper . ivy--regex-ignore-order) ivy-re-builders-alist)
  (push '(counsel-M-x . ivy--regex-ignore-order) ivy-re-builders-alist)

  ;; Set minibuffer height for different commands
  (setf (alist-get 'counsel-projectile-ag ivy-height-alist) 15)
  (setf (alist-get 'counsel-projectile-rg ivy-height-alist) 15)
  (setf (alist-get 'swiper ivy-height-alist) 15)
  (setf (alist-get 'counsel-switch-buffer ivy-height-alist) 7))

(use-package ivy-hydra
  :defer t
  :after hydra)

(use-package ivy-rich
  :init
  (ivy-rich-mode 1)
  :after counsel
  :config
  (setq ivy-format-function #'ivy-format-function-line)
  (setq ivy-rich-display-transformers-list
        (plist-put ivy-rich-display-transformers-list
                   'ivy-switch-buffer
                   '(:columns
                     ((ivy-rich-candidate (:width 40))
                      (ivy-rich-switch-buffer-indicators (:width 4 :face error :align right)); return the buffer indicators
                      (ivy-rich-switch-buffer-major-mode (:width 12 :face warning))          ; return the major mode info
                      (ivy-rich-switch-buffer-project (:width 15 :face success))             ; return project name using `projectile'
                      (ivy-rich-switch-buffer-path (:width (lambda (x) (ivy-rich-switch-buffer-shorten-path x (ivy-rich-minibuffer-width 0.3))))))  ; return file path relative to project root or `default-directory' if project is nil
                     :predicate
                     (lambda (cand)
                       (if-let ((buffer (get-buffer cand)))
                           ;; Don't mess with EXWM buffers
                           (with-current-buffer buffer
                             (not (derived-mode-p 'exwm-mode)))))))))

(use-package counsel
  :demand t
  :bind (("M-x" . counsel-M-x)
         ("C-x b" . counsel-ibuffer)
         ("C-x C-f" . counsel-find-file)
         ;; ("C-M-j" . counsel-switch-buffer)
         ("C-M-l" . counsel-imenu)
         :map minibuffer-local-map
         ("C-r" . 'counsel-minibuffer-history))
  :custom
  (counsel-linux-app-format-function #'counsel-linux-app-format-function-name-only)
  :config
  (setq ivy-initial-inputs-alist nil)) ;; Don't start searches with ^

(use-package flx  ;; Improves sorting for fuzzy-matched results
  :after ivy
  :defer t
  :init
  (setq ivy-flx-limit 10000))

(use-package wgrep)

(use-package ivy-posframe
  :disabled
  :custom
  (ivy-posframe-width      115)
  (ivy-posframe-min-width  115)
  (ivy-posframe-height     10)
  (ivy-posframe-min-height 10)
  :config
  (setq ivy-posframe-display-functions-alist '((t . ivy-posframe-display-at-frame-center)))
  (setq ivy-posframe-parameters '((parent-frame . nil)
                                  (left-fringe . 8)
                                  (right-fringe . 8)))
  (ivy-posframe-mode 1))

(use-package prescient
  :after counsel
  :config
  (prescient-persist-mode 1))

(use-package ivy-prescient
  :after prescient
  :config
  (ivy-prescient-mode 1))

(dw/leader-key-def
  "r"   '(ivy-resume :which-key "ivy resume")
  "f"   '(:ignore t :which-key "files")
  "ff"  '(counsel-find-file :which-key "open file")
  "C-f" 'counsel-find-file
  "fr"  '(counsel-recentf :which-key "recent files")
  "fR"  '(revert-buffer :which-key "revert file")
  "fj"  '(counsel-file-jump :which-key "jump to file"))

Guix Packages


"emacs-ivy"
"emacs-ivy-rich"
"emacs-counsel"
;; "emacs-ivy-posframe"
"emacs-prescient"
"emacs-flx"
"emacs-wgrep"

Completion System

Trying this as an alternative to Ivy and Counsel.

Preserve Minibuffer History with savehist-mode


(use-package savehist
  :config
  (setq history-length 25)
  (savehist-mode 1))

  ;; Individual history elements can be configured separately
  ;;(put 'minibuffer-history 'history-length 25)
  ;;(put 'evil-ex-history 'history-length 50)
  ;;(put 'kill-ring 'history-length 25))

Completions with Vertico


(defun dw/minibuffer-backward-kill (arg)
  "When minibuffer is completing a file name delete up to parent
folder, otherwise delete a word"
  (interactive "p")
  (if minibuffer-completing-file-name
      ;; Borrowed from https://github.com/raxod502/selectrum/issues/498#issuecomment-803283608
      (if (string-match-p "/." (minibuffer-contents))
          (zap-up-to-char (- arg) ?/)
        (delete-minibuffer-contents))
      (backward-kill-word arg)))

(use-package vertico
  :straight '(vertico :host github
                      :repo "minad/vertico"
                      :branch "main")
  :bind (:map vertico-map
         ("C-j" . vertico-next)
         ("C-k" . vertico-previous)
         ("C-f" . vertico-exit)
         :map minibuffer-local-map
         ("M-h" . dw/minibuffer-backward-kill))
  :custom
  (vertico-cycle t)
  :custom-face
  (vertico-current ((t (:background "#3a3f5a"))))
  :init
  (vertico-mode))

Completions in Regions with Corfu


(use-package corfu
  :straight '(corfu :host github
                    :repo "minad/corfu")
  :bind (:map corfu-map
         ("C-j" . corfu-next)
         ("C-k" . corfu-previous)
         ("C-f" . corfu-insert))
  :custom
  (corfu-cycle t)
  :config
  (corfu-global-mode))

Improved Candidate Filtering with Orderless


(use-package orderless
  :straight t
  :init
  (setq completion-styles '(orderless)
        completion-category-defaults nil
        completion-category-overrides '((file (styles . (partial-completion))))))

Completions with icomplete

Tried this out for a while but at the moment I like Vertico better!


(use-package icomplete-vertical
  :disabled
  :straight t
  :demand t
  :after orderless
  :bind (:map icomplete-minibuffer-map
              ("C-j"   . icomplete-forward-completions)
              ("C-k"   . icomplete-backward-completions)
              ("C-f"   . icomplete-force-complete-and-exit)
              ("C-M-f" . icomplete-force-complete)
              ("TAB"   . icomplete-force-complete)
              ("RET"   . icomplete-force-complete-and-exit)
              ("M-h"   . backward-kill-word))
  :custom
  (completion-styles '(orderless partial-completion substring))
  (completion-category-overrides '((file (styles basic substring))))
  (read-file-name-completion-ignore-case t)
  (read-buffer-completion-ignore-case t)
  (completion-ignore-case t)
  (completion-cycling t)
  (completion-cycle-threshold 5)
  (icomplete-compute-delay 0.1)
  (icomplete-vertical-prospects-height 7)
  :custom-face
  (icomplete-first-match ((t (:foreground "LightGreen" :weight bold))))
  :config
  ;; Deal with a weird issue where the minibuffer disappears
  ;; in some cases when resize-mini-windows isn't nil
  (setq resize-mini-windows nil)

  ;; Enable icomplete and vertical completions
  (icomplete-mode)
  (icomplete-vertical-mode))

;; (use-package restricto
;;   :straight '(restricto :host github
;;                         :repo "oantolin/restricto")
;;   :after vertico
;;   :demand t
;;   :bind (:map vertico-map
;;          ("S-SPC" . restricto-narrow))
;;   :config
;;   (restricto-mode))

Consult Commands

Consult provides a lot of useful completion commands similar to Ivy's Counsel.


(defun dw/get-project-root ()
  (when (fboundp 'projectile-project-root)
    (projectile-project-root)))

(use-package consult
  :straight t
  :demand t
  :bind (("C-s" . consult-line)
         ("C-M-l" . consult-imenu)
         ("C-M-j" . persp-switch-to-buffer*)
         :map minibuffer-local-map
         ("C-r" . consult-history))
  :custom
  (consult-project-root-function #'dw/get-project-root)
  (completion-in-region-function #'consult-completion-in-region)
  :config
  (consult-preview-mode))

Completion Annotations with Marginalia

Marginalia provides helpful annotations for various types of minibuffer completions. You can think of it as a replacement of ivy-rich.


(use-package marginalia
  :after vertico
  :straight t
  :custom
  (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
  :init
  (marginalia-mode))

Completion Actions with Embark


(use-package embark
  :straight t
  :bind (("C-S-a" . embark-act)
         :map minibuffer-local-map
         ("C-d" . embark-act))
  :config

  ;; Show Embark actions via which-key
  (setq embark-action-indicator
        (lambda (map)
          (which-key--show-keymap "Embark" map nil nil 'no-paging)
          #'which-key--hide-popup-ignore-command)
        embark-become-indicator embark-action-indicator))

;; (use-package embark-consult
;;   :straight '(embark-consult :host github
;;                              :repo "oantolin/embark"
;;                              :files ("embark-consult.el"))
;;   :after (embark consult)
;;   :demand t
;;   :hook
;;   (embark-collect-mode . embark-consult-preview-minor-mode))

Launching apps


(use-package app-launcher)

Guix Packages


"emacs-app-launcher"

Selectrum

Selectrum is good, but I'm enjoying the simplicity of Vertico at the moment!


(use-package selectrum
  :disabled
  :bind (("C-M-r" . selectrum-repeat)
         :map selectrum-minibuffer-map
         ("C-r" . selectrum-select-from-history)
         ("C-j" . selectrum-next-candidate)
         ("C-k" . selectrum-previous-candidate)
         :map minibuffer-local-map
         ("M-h" . backward-kill-word))
  :custom
  (selectrum-fix-minibuffer-height t)
  (selectrum-num-candidates-displayed 7)
  (selectrum-refine-candidates-function #'orderless-filter)
  (selectrum-highlight-candidates-function #'orderless-highlight-matches)
  :custom-face
  (selectrum-current-candidate ((t (:background "#3a3f5a"))))
  :init
  (selectrum-mode 1))

Guix Packages


"emacs-selectrum"

Jumping with Avy


(use-package avy
  :commands (avy-goto-char avy-goto-word-0 avy-goto-line))

(dw/leader-key-def
  "j"   '(:ignore t :which-key "jump")
  "jj"  '(avy-goto-char :which-key "jump to char")
  "jw"  '(avy-goto-word-0 :which-key "jump to word")
  "jl"  '(avy-goto-line :which-key "jump to line"))

Guix Packages


"emacs-avy"

Buffer Management with Bufler

Bufler is an excellent package by alphapapa which enables you to automatically group all of your Emacs buffers into workspaces by defining a series of grouping rules. Once you have your groups defined (or use the default configuration which is quite good already), you can use the bufler-workspace-frame-set command to focus your current Emacs frame on a particular workspace so that bufler-switch-buffer will only show buffers from that workspace. In my case, this allows me to dedicate an EXWM workspace to a specific Bufler workspace so that only see the buffers I care about in that EXWM workspace.

I'm trying to figure out how to integrate Bufler with Ivy more effectively (buffer previewing, alternate actions, etc), will update this config once I've done that.


(use-package bufler
  :disabled
  :straight t
  :bind (("C-M-j" . bufler-switch-buffer)
         ("C-M-k" . bufler-workspace-frame-set))
  :config
  (evil-collection-define-key 'normal 'bufler-list-mode-map
    (kbd "RET")   'bufler-list-buffer-switch
    (kbd "M-RET") 'bufler-list-buffer-peek
    "D"           'bufler-list-buffer-kill)

  (setf bufler-groups
        (bufler-defgroups
          ;; Subgroup collecting all named workspaces.
          (group (auto-workspace))
          ;; Subgroup collecting buffers in a projectile project.
          (group (auto-projectile))
          ;; Grouping browser windows
          (group
           (group-or "Browsers"
                     (name-match "Vimb" (rx bos "vimb"))
                     (name-match "Qutebrowser" (rx bos "Qutebrowser"))
                     (name-match "Chromium" (rx bos "Chromium"))))
          (group
           (group-or "Chat"
                     (mode-match "Telega" (rx bos "telega-"))))
          (group
           ;; Subgroup collecting all `help-mode' and `info-mode' buffers.
           (group-or "Help/Info"
                     (mode-match "*Help*" (rx bos (or "help-" "helpful-")))
                     ;; (mode-match "*Helpful*" (rx bos "helpful-"))
                     (mode-match "*Info*" (rx bos "info-"))))
          (group
           ;; Subgroup collecting all special buffers (i.e. ones that are not
           ;; file-backed), except `magit-status-mode' buffers (which are allowed to fall
           ;; through to other groups, so they end up grouped with their project buffers).
           (group-and "*Special*"
                      (name-match "**Special**"
                                  (rx bos "*" (or "Messages" "Warnings" "scratch" "Backtrace" "Pinentry") "*"))
                      (lambda (buffer)
                        (unless (or (funcall (mode-match "Magit" (rx bos "magit-status"))
                                             buffer)
                                    (funcall (mode-match "Dired" (rx bos "dired"))
                                             buffer)
                                    (funcall (auto-file) buffer))
                          "*Special*"))))
          ;; Group remaining buffers by major mode.
          (auto-mode))))

Window Management

Frame Scaling / Zooming

The keybindings for this are C+M+- and C+M+=.


(use-package default-text-scale
  :defer 1
  :config
  (default-text-scale-mode))

Guix Packages


"emacs-default-text-scale"

Window Selection with ace-window

ace-window helps with easily switching between windows based on a predefined set of keys used to identify each.


(use-package ace-window
  :bind (("M-o" . ace-window))
  :custom
  (aw-scope 'frame)
  (aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  (aw-minibuffer-flag t)
  :config
  (ace-window-display-mode 1))

Guix Packages


"emacs-ace-window"

Window History with winner-mode


(use-package winner
  :after evil
  :config
  (winner-mode)
  (define-key evil-window-map "u" 'winner-undo)
  (define-key evil-window-map "U" 'winner-redo))

Set Margins for Modes


;; (defun dw/center-buffer-with-margins ()
;;   (let ((margin-size (/ (- (frame-width) 80) 3)))
;;     (set-window-margins nil margin-size margin-size)))

(defun dw/org-mode-visual-fill ()
  (setq visual-fill-column-width 110
        visual-fill-column-center-text t)
  (visual-fill-column-mode 1))

(use-package visual-fill-column
  :defer t
  :hook (org-mode . dw/org-mode-visual-fill))

Guix Packages


"emacs-visual-fill-column"

Control Buffer Placement

Emacs' default buffer placement algorithm is pretty disruptive if you like setting up window layouts a certain way in your workflow. The display-buffer-alist video controls this behavior and you can customize it to prevent Emacs from popping up new windows when you run commands.


(setq display-buffer-base-action
      '(display-buffer-reuse-mode-window
        display-buffer-reuse-window
        display-buffer-same-window))

;; If a popup does happen, don't resize windows to be equal-sized
(setq even-window-sizes nil)

Expand Region

This module is absolutely necessary for working inside of Emacs Lisp files, especially when trying to some parent of an expression (like a setq). Makes tweaking Org agenda views much less annoying.


(use-package expand-region
  :if (not dw/is-termux)
  :bind (("M-[" . er/expand-region)
         ("C-(" . er/mark-outside-pairs)))

Guix Packages


"emacs-expand-region"

Credential Management

I use pass to manage all of my passwords locally. I also use auth-source-pass as the primary auth-source provider so that all passwords are stored in a single place.


(use-package password-store
  :config
  (setq password-store-password-length 12))

(use-package auth-source-pass
  :config
  (auth-source-pass-enable))

(dw/leader-key-def
  "ap" '(:ignore t :which-key "pass")
  "app" 'password-store-copy
  "api" 'password-store-insert
  "apg" 'password-store-generate)

Guix Packages


"emacs-password-store"
"emacs-auth-source-pass"

File Browsing

Dired


(use-package all-the-icons-dired)

(use-package dired
  :ensure nil
  :straight nil
  :defer 1
  :commands (dired dired-jump)
  :config
  (setq dired-listing-switches "-agho --group-directories-first"
        dired-omit-files "^\\.[^.].*"
        dired-omit-verbose nil
        dired-hide-details-hide-symlink-targets nil
        delete-by-moving-to-trash t)

  (autoload 'dired-omit-mode "dired-x")

  (add-hook 'dired-load-hook
            (lambda ()
              (interactive)
              (dired-collapse)))

  (add-hook 'dired-mode-hook
            (lambda ()
              (interactive)
              (dired-omit-mode 1)
              (dired-hide-details-mode 1)
              (unless (or dw/is-termux
                          (s-equals? "/gnu/store/" (expand-file-name default-directory)))
                (all-the-icons-dired-mode 1))
              (hl-line-mode 1)))

  (use-package dired-rainbow
    :defer 2
    :config
    (dired-rainbow-define-chmod directory "#6cb2eb" "d.*")
    (dired-rainbow-define html "#eb5286" ("css" "less" "sass" "scss" "htm" "html" "jhtm" "mht" "eml" "mustache" "xhtml"))
    (dired-rainbow-define xml "#f2d024" ("xml" "xsd" "xsl" "xslt" "wsdl" "bib" "json" "msg" "pgn" "rss" "yaml" "yml" "rdata"))
    (dired-rainbow-define document "#9561e2" ("docm" "doc" "docx" "odb" "odt" "pdb" "pdf" "ps" "rtf" "djvu" "epub" "odp" "ppt" "pptx"))
    (dired-rainbow-define markdown "#ffed4a" ("org" "etx" "info" "markdown" "md" "mkd" "nfo" "pod" "rst" "tex" "textfile" "txt"))
    (dired-rainbow-define database "#6574cd" ("xlsx" "xls" "csv" "accdb" "db" "mdb" "sqlite" "nc"))
    (dired-rainbow-define media "#de751f" ("mp3" "mp4" "mkv" "MP3" "MP4" "avi" "mpeg" "mpg" "flv" "ogg" "mov" "mid" "midi" "wav" "aiff" "flac"))
    (dired-rainbow-define image "#f66d9b" ("tiff" "tif" "cdr" "gif" "ico" "jpeg" "jpg" "png" "psd" "eps" "svg"))
    (dired-rainbow-define log "#c17d11" ("log"))
    (dired-rainbow-define shell "#f6993f" ("awk" "bash" "bat" "sed" "sh" "zsh" "vim"))
    (dired-rainbow-define interpreted "#38c172" ("py" "ipynb" "rb" "pl" "t" "msql" "mysql" "pgsql" "sql" "r" "clj" "cljs" "scala" "js"))
    (dired-rainbow-define compiled "#4dc0b5" ("asm" "cl" "lisp" "el" "c" "h" "c++" "h++" "hpp" "hxx" "m" "cc" "cs" "cp" "cpp" "go" "f" "for" "ftn" "f90" "f95" "f03" "f08" "s" "rs" "hi" "hs" "pyc" ".java"))
    (dired-rainbow-define executable "#8cc4ff" ("exe" "msi"))
    (dired-rainbow-define compressed "#51d88a" ("7z" "zip" "bz2" "tgz" "txz" "gz" "xz" "z" "Z" "jar" "war" "ear" "rar" "sar" "xpi" "apk" "xz" "tar"))
    (dired-rainbow-define packaged "#faad63" ("deb" "rpm" "apk" "jad" "jar" "cab" "pak" "pk3" "vdf" "vpk" "bsp"))
    (dired-rainbow-define encrypted "#ffed4a" ("gpg" "pgp" "asc" "bfe" "enc" "signature" "sig" "p12" "pem"))
    (dired-rainbow-define fonts "#6cb2eb" ("afm" "fon" "fnt" "pfb" "pfm" "ttf" "otf"))
    (dired-rainbow-define partition "#e3342f" ("dmg" "iso" "bin" "nrg" "qcow" "toast" "vcd" "vmdk" "bak"))
    (dired-rainbow-define vc "#0074d9" ("git" "gitignore" "gitattributes" "gitmodules"))
    (dired-rainbow-define-chmod executable-unix "#38c172" "-.*x.*"))

  (use-package dired-single
    :defer t)

  (use-package dired-ranger
    :defer t)

  (use-package dired-collapse
    :defer t)

  (evil-collection-define-key 'normal 'dired-mode-map
    "h" 'dired-single-up-directory
    "H" 'dired-omit-mode
    "l" 'dired-single-buffer
    "y" 'dired-ranger-copy
    "X" 'dired-ranger-move
    "p" 'dired-ranger-paste))

;; (defun dw/dired-link (path)
;;   (lexical-let ((target path))
;;     (lambda () (interactive) (message "Path: %s" target) (dired target))))

;; (dw/leader-key-def
;;   "d"   '(:ignore t :which-key "dired")
;;   "dd"  '(dired :which-key "Here")
;;   "dh"  `(,(dw/dired-link "~") :which-key "Home")
;;   "dn"  `(,(dw/dired-link "~/Notes") :which-key "Notes")
;;   "do"  `(,(dw/dired-link "~/Downloads") :which-key "Downloads")
;;   "dp"  `(,(dw/dired-link "~/Pictures") :which-key "Pictures")
;;   "dv"  `(,(dw/dired-link "~/Videos") :which-key "Videos")
;;   "d."  `(,(dw/dired-link "~/.dotfiles") :which-key "dotfiles")
;;   "de"  `(,(dw/dired-link "~/.emacs.d") :which-key ".emacs.d"))

Guix Packages


"emacs-dired-single"
"emacs-dired-hacks"
"emacs-all-the-icons-dired"

Opening Files Externally


(use-package openwith
  :if (not dw/is-termux)
  :config
  (setq openwith-associations
        (list
          (list (openwith-make-extension-regexp
                '("mpg" "mpeg" "mp3" "mp4"
                  "avi" "wmv" "wav" "mov" "flv"
                  "ogm" "ogg" "mkv"))
                "mpv"
                '(file))
          (list (openwith-make-extension-regexp
                '("xbm" "pbm" "pgm" "ppm" "pnm"
                  "png" "gif" "bmp" "tif" "jpeg")) ;; Removed jpg because Telega was
                  ;; causing feh to be opened...
                  "feh"
                  '(file))
          (list (openwith-make-extension-regexp
                '("pdf"))
                "zathura"
                '(file)))))

Guix Packages


"emacs-openwith"

Org Mode

Org Configuration

Set up Org Mode with a baseline configuration. The following sections will add more things to it.


;; TODO: Mode this to another section
(setq-default fill-column 80)

;; Turn on indentation and auto-fill mode for Org files
(defun dw/org-mode-setup ()
  (org-indent-mode)
  (variable-pitch-mode 1)
  (auto-fill-mode 0)
  (visual-line-mode 1)
  (setq evil-auto-indent nil)
  (diminish org-indent-mode))

;; Make sure Straight pulls Org from Guix
(when dw/is-guix-system
  (straight-use-package '(org :type built-in)))

(use-package org
  :defer t
  :hook (org-mode . dw/org-mode-setup)
  :config
  (setq org-ellipsis " ▾"
        org-hide-emphasis-markers t
        org-src-fontify-natively t
        org-fontify-quote-and-verse-blocks t
        org-src-tab-acts-natively t
        org-edit-src-content-indentation 2
        org-hide-block-startup nil
        org-src-preserve-indentation nil
        org-startup-folded 'content
        org-cycle-separator-lines 2)

  (setq org-modules
    '(org-crypt
        org-habit
        org-bookmark
        org-eshell
        org-irc))

  (setq org-refile-targets '((nil :maxlevel . 1)
                             (org-agenda-files :maxlevel . 1)))

  (setq org-outline-path-complete-in-steps nil)
  (setq org-refile-use-outline-path t)

  (evil-define-key '(normal insert visual) org-mode-map (kbd "C-j") 'org-next-visible-heading)
  (evil-define-key '(normal insert visual) org-mode-map (kbd "C-k") 'org-previous-visible-heading)

  (evil-define-key '(normal insert visual) org-mode-map (kbd "M-j") 'org-metadown)
  (evil-define-key '(normal insert visual) org-mode-map (kbd "M-k") 'org-metaup)

  (org-babel-do-load-languages
    'org-babel-load-languages
    '((emacs-lisp . t)
      (ledger . t)))

  (push '("conf-unix" . conf-unix) org-src-lang-modes)

  ;; NOTE: Subsequent sections are still part of this use-package block!

Guix Packages


"emacs-org"

Workflow Configuration

I document and configure my org-mode workflow in a separate document: Workflow.org


(require 'dw-org)
(require 'dw-workflow)

Fonts and Bullets

Use bullet characters instead of asterisks, plus set the header font sizes to something more palatable. A fair amount of inspiration has been taken from this blog post.


(use-package org-superstar
  :if (not dw/is-termux)
  :after org
  :hook (org-mode . org-superstar-mode)
  :custom
  (org-superstar-remove-leading-stars t)
  (org-superstar-headline-bullets-list '("◉" "○" "●" "○" "●" "○" "●")))

;; Replace list hyphen with dot
;; (font-lock-add-keywords 'org-mode
;;                         '(("^ *\\([-]\\) "
;;                             (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•"))))))

;; Increase the size of various headings
(set-face-attribute 'org-document-title nil :font "Iosevka Aile" :weight 'bold :height 1.3)
(dolist (face '((org-level-1 . 1.2)
                (org-level-2 . 1.1)
                (org-level-3 . 1.05)
                (org-level-4 . 1.0)
                (org-level-5 . 1.1)
                (org-level-6 . 1.1)
                (org-level-7 . 1.1)
                (org-level-8 . 1.1)))
  (set-face-attribute (car face) nil :font "Iosevka Aile" :weight 'medium :height (cdr face)))

;; Make sure org-indent face is available
(require 'org-indent)

;; Ensure that anything that should be fixed-pitch in Org files appears that way
(set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch)
(set-face-attribute 'org-table nil  :inherit 'fixed-pitch)
(set-face-attribute 'org-formula nil  :inherit 'fixed-pitch)
(set-face-attribute 'org-code nil   :inherit '(shadow fixed-pitch))
(set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch))
(set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
(set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
(set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch))
(set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch)

;; Get rid of the background on column views
(set-face-attribute 'org-column nil :background nil)
(set-face-attribute 'org-column-title nil :background nil)

;; TODO: Others to consider
;; '(org-document-info-keyword ((t (:inherit (shadow fixed-pitch)))))
;; '(org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch)))))
;; '(org-property-value ((t (:inherit fixed-pitch))) t)
;; '(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch)))))
;; '(org-table ((t (:inherit fixed-pitch :foreground "#83a598"))))
;; '(org-tag ((t (:inherit (shadow fixed-pitch) :weight bold :height 0.8))))
;; '(org-verbatim ((t (:inherit (shadow fixed-pitch))))))

Guix Packages


"emacs-org-bullets"
"emacs-org-superstar"

Block Templates

These templates enable you to type things like <el and then hit Tab to expand the template. More documentation can be found at the Org Mode Easy Templates documentation page.


;; This is needed as of Org 9.2
(require 'org-tempo)

(add-to-list 'org-structure-template-alist '("sh" . "src sh"))
(add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
(add-to-list 'org-structure-template-alist '("sc" . "src scheme"))
(add-to-list 'org-structure-template-alist '("ts" . "src typescript"))
(add-to-list 'org-structure-template-alist '("py" . "src python"))
(add-to-list 'org-structure-template-alist '("go" . "src go"))
(add-to-list 'org-structure-template-alist '("yaml" . "src yaml"))
(add-to-list 'org-structure-template-alist '("json" . "src json"))

Pomodoro


(use-package org-pomodoro
  :after org
  :config
  (setq org-pomodoro-start-sound "~/.dotfiles/.emacs.d/sounds/focus_bell.wav")
  (setq org-pomodoro-short-break-sound "~/.dotfiles/.emacs.d/sounds/three_beeps.wav")
  (setq org-pomodoro-long-break-sound "~/.dotfiles/.emacs.d/sounds/three_beeps.wav")
  (setq org-pomodoro-finished-sound "~/.dotfiles/.emacs.d/sounds/meditation_bell.wav")

  (dw/leader-key-def
    "op"  '(org-pomodoro :which-key "pomodoro")))

Guix Packages


"emacs-org-pomodoro"

Protocol

This is probably not needed if I plan to use custom functions that are invoked through emacsclient.


(require 'org-protocol)

Searching


(defun dw/search-org-files ()
  (interactive)
  (counsel-rg "" "~/Notes" nil "Search Notes: "))

Bindings


(use-package evil-org
  :after org
  :hook ((org-mode . evil-org-mode)
         (org-agenda-mode . evil-org-mode)
         (evil-org-mode . (lambda () (evil-org-set-key-theme '(navigation todo insert textobjects additional)))))
  :config
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys))

(dw/leader-key-def
  "o"   '(:ignore t :which-key "org mode")

  "oi"  '(:ignore t :which-key "insert")
  "oil" '(org-insert-link :which-key "insert link")

  "on"  '(org-toggle-narrow-to-subtree :which-key "toggle narrow")

  "os"  '(dw/counsel-rg-org-files :which-key "search notes")

  "oa"  '(org-agenda :which-key "status")
  "ot"  '(org-todo-list :which-key "todos")
  "oc"  '(org-capture t :which-key "capture")
  "ox"  '(org-export-dispatch t :which-key "export"))

Guix Packages


"emacs-evil-org"

End use-package org-mode


;; This ends the use-package org-mode block
)

Update Table of Contents on Save

It's nice to have a table of contents section for long literate configuration files (like this one!) so I use org-make-toc to automatically update the ToC in any header with a property named TOC.


(use-package org-make-toc
  :hook (org-mode . org-make-toc-mode))

Guix Packages


"emacs-org-make-toc"

Calendar Sync


;; (use-package org-gcal
;;   :after org
;;   :config

;;   (setq org-gcal-client-id (password-store-get "API/Google/daviwil-emacs-id")
;;         org-gcal-client-secret (password-store-get "API/Google/daviwil-emacs-secret")
;;         org-gcal-file-alist `(("daviwil@github.com" . ,(dw/org-path "Calendar.org"))
;;                               (,(password-store-get "Misc/Calendars/GitHub/AtomTeam") . ,(dw/org-path "Calendar.org"))
;;                              )))

;; (dw/leader-key-def
;;   "ac"  '(:ignore t :which-key "calendar")
;;   "acs" '(org-gcal-fetch :which-key "sync"))

(use-package org-caldav
  :disabled
  :defer t
  :init
  (setq org-caldav-url "https://caldav.fastmail.com/dav/calendars/user/daviwil@fastmail.fm/"
        org-caldav-inbox nil
        org-caldav-calendar-id nil
        org-caldav-calendars
         '((:calendar-id "fe098bfb-0726-4e10-bff2-55f8278c8a56"
            :inbox "~/Notes/Calendar/Personal.org")
           (:calendar-id "8f150437-cc57-4ba0-9200-d1d98389e2e4"
            :inbox "~/Notes/Calendar/Work.org"))
        org-caldav-delete-org-entries 'always
        org-caldav-delete-calendar-entries 'never))

Guix Packages


"emacs-org-caldav"

Reminders


;; (use-package org-wild-notifier
;;   :after org
;;   :config
;;   ; Make sure we receive notifications for non-TODO events
;;   ; like those synced from Google Calendar
;;   (setq org-wild-notifier-keyword-whitelist nil)
;;   (setq org-wild-notifier-notification-title "Agenda Reminder")
;;   (setq org-wild-notifier-alert-time 15)
;;   (org-wild-notifier-mode))

Presentations

org-present

org-present is the package I use for giving presentations in Emacs. I like it because it's simple and allows me to customize the display of it pretty easily.


(defun dw/org-present-prepare-slide ()
  (org-overview)
  (org-show-entry)
  (org-show-children))

(defun dw/org-present-hook ()
  (setq-local face-remapping-alist '((default (:height 1.5) variable-pitch)
                                     (header-line (:height 4.5) variable-pitch)
                                     (org-code (:height 1.55) org-code)
                                     (org-verbatim (:height 1.55) org-verbatim)
                                     (org-block (:height 1.25) org-block)
                                     (org-block-begin-line (:height 0.7) org-block)))
  (setq header-line-format " ")
  (org-display-inline-images)
  (dw/org-present-prepare-slide))

(defun dw/org-present-quit-hook ()
  (setq-local face-remapping-alist '((default variable-pitch default)))
  (setq header-line-format nil)
  (org-present-small)
  (org-remove-inline-images))

(defun dw/org-present-prev ()
  (interactive)
  (org-present-prev)
  (dw/org-present-prepare-slide))

(defun dw/org-present-next ()
  (interactive)
  (org-present-next)
  (dw/org-present-prepare-slide))

(use-package org-present
  :bind (:map org-present-mode-keymap
         ("C-c C-j" . dw/org-present-next)
         ("C-c C-k" . dw/org-present-prev))
  :hook ((org-present-mode . dw/org-present-hook)
         (org-present-mode-quit . dw/org-present-quit-hook)))

org-tree-slide

I previously used org-tree-slide for presentations before trying out org-present. I'm keeping my old configuration around here just in case I need to use it again!


(defun dw/org-start-presentation ()
  (interactive)
  (org-tree-slide-mode 1)
  (setq text-scale-mode-amount 3)
  (text-scale-mode 1))

(defun dw/org-end-presentation ()
  (interactive)
  (text-scale-mode 0)
  (org-tree-slide-mode 0))

(use-package org-tree-slide
  :defer t
  :after org
  :commands org-tree-slide-mode
  :config
  (evil-define-key 'normal org-tree-slide-mode-map
    (kbd "q") 'dw/org-end-presentation
    (kbd "C-j") 'org-tree-slide-move-next-tree
    (kbd "C-k") 'org-tree-slide-move-previous-tree)
  (setq org-tree-slide-slide-in-effect nil
        org-tree-slide-activate-message "Presentation started."
        org-tree-slide-deactivate-message "Presentation ended."
        org-tree-slide-header t))

Guix Packages


"emacs-org-tree-slide"
"emacs-org-present"

Org Roam


(use-package org-roam
  :straight t
  :hook
  (after-init . org-roam-mode)
  :custom
  (org-roam-directory "~/Notes/Roam/")
  (org-roam-completion-everywhere t)
  (org-roam-completion-system 'default)
  (org-roam-capture-templates
    '(("d" "default" plain
       #'org-roam-capture--get-point
       "%?"
       :file-name "%<%Y%m%d%H%M%S>-${slug}"
       :head "#+title: ${title}\n"
       :unnarrowed t)
      ("ll" "link note" plain
       #'org-roam-capture--get-point
       "* %^{Link}"
       :file-name "Inbox"
       :olp ("Links")
       :unnarrowed t
       :immediate-finish)
      ("lt" "link task" entry
       #'org-roam-capture--get-point
       "* TODO %^{Link}"
       :file-name "Inbox"
       :olp ("Tasks")
       :unnarrowed t
       :immediate-finish)))
  (org-roam-dailies-directory "Journal/")
  (org-roam-dailies-capture-templates
    '(("d" "default" entry
       #'org-roam-capture--get-point
       "* %?"
       :file-name "Journal/%<%Y-%m-%d>"
       :head "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")
      ("t" "Task" entry
       #'org-roam-capture--get-point
       "* TODO %?\n  %U\n  %a\n  %i"
       :file-name "Journal/%<%Y-%m-%d>"
       :olp ("Tasks")
       :empty-lines 1
       :head "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")
      ("j" "journal" entry
       #'org-roam-capture--get-point
       "* %<%I:%M %p> - Journal  :journal:\n\n%?\n\n"
       :file-name "Journal/%<%Y-%m-%d>"
       :olp ("Log")
       :head "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")
      ("l" "log entry" entry
       #'org-roam-capture--get-point
       "* %<%I:%M %p> - %?"
       :file-name "Journal/%<%Y-%m-%d>"
       :olp ("Log")
       :head "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")
      ("m" "meeting" entry
       #'org-roam-capture--get-point
       "* %<%I:%M %p> - %^{Meeting Title}  :meetings:\n\n%?\n\n"
       :file-name "Journal/%<%Y-%m-%d>"
       :olp ("Log")
       :head "#+title: %<%Y-%m-%d %a>\n\n[[roam:%<%Y-%B>]]\n\n")))
  :bind (:map org-roam-mode-map
          (("C-c n l"   . org-roam)
           ("C-c n f"   . org-roam-find-file)
           ("C-c n d"   . org-roam-dailies-find-date)
           ("C-c n c"   . org-roam-dailies-capture-today)
           ("C-c n C r" . org-roam-dailies-capture-tomorrow)
           ("C-c n t"   . org-roam-dailies-find-today)
           ("C-c n y"   . org-roam-dailies-find-yesterday)
           ("C-c n r"   . org-roam-dailies-find-tomorrow)
           ("C-c n g"   . org-roam-graph))
         :map org-mode-map
         (("C-c n i" . org-roam-insert))
         (("C-c n I" . org-roam-insert-immediate))))

Deft


(use-package deft
  :commands (deft)
  :config (setq deft-directory "~/Notes/Roam"
                deft-recursive t
                deft-extensions '("md" "org")))

Guix Packages


;; The packaged org-roam is missing some things!
;; "emacs-org-roam"
"emacs-deft"

Auto-show Markup Symbols

This package makes it much easier to edit Org documents when org-hide-emphasis-markers is turned on. It temporarily shows the emphasis markers around certain markup elements when you place your cursor inside of them. No more fumbling around with = and * characters!


(use-package org-appear
  :hook (org-mode . org-appear-mode))

Guix Packages


"emacs-org-appear"

Addons to Try

Website Management

daviwil.com

I generate and publish my personal site daviwil.com using org-mode using dw/generate-site and dw/publish-site:


(defun dw/generate-site ()
  (interactive)
  (start-process-shell-command "emacs" nil "emacs --batch -l ~/Projects/Writing/Blog/publish.el --funcall dw/publish"))

Development

Configuration for various programming languages and dev tools that I use.

Git

Magit

https://magit.vc/manual/magit/


(use-package magit
  :bind ("C-M-;" . magit-status)
  :commands (magit-status magit-get-current-branch)
  :custom
  (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))

(dw/leader-key-def
  "g"   '(:ignore t :which-key "git")
  "gs"  'magit-status
  "gd"  'magit-diff-unstaged
  "gc"  'magit-branch-or-checkout
  "gl"   '(:ignore t :which-key "log")
  "glc" 'magit-log-current
  "glf" 'magit-log-buffer-file
  "gb"  'magit-branch
  "gP"  'magit-push-current
  "gp"  'magit-pull-branch
  "gf"  'magit-fetch
  "gF"  'magit-fetch-all
  "gr"  'magit-rebase)

Guix Packages


"emacs-magit"
"emacs-magit-todos"

Forge


(use-package forge
  :disabled)

Guix Packages


"emacs-forge"

magit-todos

This is an interesting extension to Magit that shows a TODOs section in your git status buffer containing all lines with TODO (or other similar words) in files contained within the repo. More information at the GitHub repo.


(use-package magit-todos
  :defer t)

Guix Packages


"emacs-magit-todos"

git-link


(use-package git-link
  :commands git-link
  :config
  (setq git-link-open-in-browser t)
  (dw/leader-key-def
    "gL"  'git-link))

Guix Packages


"emacs-git-link"

Git Gutter


(use-package git-gutter
  :straight git-gutter-fringe
  :diminish
  :hook ((text-mode . git-gutter-mode)
         (prog-mode . git-gutter-mode))
  :config
  (setq git-gutter:update-interval 2)
  (unless dw/is-termux
    (require 'git-gutter-fringe)
    (set-face-foreground 'git-gutter-fr:added "LightGreen")
    (fringe-helper-define 'git-gutter-fr:added nil
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      ".........."
      ".........."
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      ".........."
      ".........."
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX")

    (set-face-foreground 'git-gutter-fr:modified "LightGoldenrod")
    (fringe-helper-define 'git-gutter-fr:modified nil
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      ".........."
      ".........."
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      ".........."
      ".........."
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX")

    (set-face-foreground 'git-gutter-fr:deleted "LightCoral")
    (fringe-helper-define 'git-gutter-fr:deleted nil
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      ".........."
      ".........."
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      ".........."
      ".........."
      "XXXXXXXXXX"
      "XXXXXXXXXX"
      "XXXXXXXXXX"))

  ;; These characters are used in terminal mode
  (setq git-gutter:modified-sign "≡")
  (setq git-gutter:added-sign "≡")
  (setq git-gutter:deleted-sign "≡")
  (set-face-foreground 'git-gutter:added "LightGreen")
  (set-face-foreground 'git-gutter:modified "LightGoldenrod")
  (set-face-foreground 'git-gutter:deleted "LightCoral"))

Guix Packages


"emacs-git-gutter"
"emacs-git-gutter-fringe"

Send e-mail for Git patches

OK, this isn't Emacs configuration, but it's relevant to development!

Guix Packages


"git"
"git:send-email"

Projectile

Initial Setup


(defun dw/switch-project-action ()
  "Switch to a workspace with the project name and start `magit-status'."
  ;; TODO: Switch to EXWM workspace 1?
  (persp-switch (projectile-project-name))
  (magit-status))

(use-package projectile
  :diminish projectile-mode
  :config (projectile-mode)
  :demand t
  :bind-keymap
  ("C-c p" . projectile-command-map)
  :init
  (when (file-directory-p "~/Projects/Code")
    (setq projectile-project-search-path '("~/Projects/Code")))
  (setq projectile-switch-project-action #'dw/switch-project-action))

(use-package counsel-projectile
  :after projectile
  :bind (("C-M-p" . counsel-projectile-find-file))
  :config
  (counsel-projectile-mode))

(dw/leader-key-def
  "pf"  'counsel-projectile-find-file
  "ps"  'counsel-projectile-switch-project
  "pF"  'counsel-projectile-rg
  ;; "pF"  'consult-ripgrep
  "pp"  'counsel-projectile
  "pc"  'projectile-compile-project
  "pd"  'projectile-dired)

Guix Packages


"emacs-projectile"
"emacs-counsel-projectile"
"ripgrep" ;; For counsel-projectile-rg
"the-silver-searcher" ;; For counsel-projectile-ag

Project Configurations

This section contains project configurations for specific projects that I can't drop a .dir-locals.el file into. Documentation on this approach can be found in the Emacs manual.


(dir-locals-set-class-variables 'Atom
  `((nil . ((projectile-project-name . "Atom")
            (projectile-project-compilation-dir . nil)
            (projectile-project-compilation-cmd . "script/build")))))

(dir-locals-set-directory-class (expand-file-name "~/Projects/Code/atom") 'Atom)

Languages

Language Server Support


;; (use-package ivy-xref
;;   :straight t
;;   :init (if (< emacs-major-version 27)
;;           (setq xref-show-xrefs-function #'ivy-xref-show-xrefs)
;;           (setq xref-show-definitions-function #'ivy-xref-show-defs)))

(use-package lsp-mode
  :straight t
  :commands lsp
  :hook ((typescript-mode js2-mode web-mode) . lsp)
  :bind (:map lsp-mode-map
         ("TAB" . completion-at-point))
  :custom (lsp-headerline-breadcrumb-enable nil))

(dw/leader-key-def
  "l"  '(:ignore t :which-key "lsp")
  "ld" 'xref-find-definitions
  "lr" 'xref-find-references
  "ln" 'lsp-ui-find-next-reference
  "lp" 'lsp-ui-find-prev-reference
  "ls" 'counsel-imenu
  "le" 'lsp-ui-flycheck-list
  "lS" 'lsp-ui-sideline-mode
  "lX" 'lsp-execute-code-action)

(use-package lsp-ui
  :straight t
  :hook (lsp-mode . lsp-ui-mode)
  :config
  (setq lsp-ui-sideline-enable t)
  (setq lsp-ui-sideline-show-hover nil)
  (setq lsp-ui-doc-position 'bottom)
  (lsp-ui-doc-show))

;; (use-package lsp-ivy
;;   :hook (lsp-mode . lsp-ivy-mode))

Guix Packages


;; "emacs-lsp-mode"
;; "emacs-lsp-ui"
;; "emacs-ivy-xref"
;; "emacs-lsp-ivy"

Debug Adapter Support


(use-package dap-mode
  :straight t
  :custom
  (lsp-enable-dap-auto-configure nil)
  :config
  (dap-ui-mode 1)
  (dap-tooltip-mode 1)
  (require 'dap-node)
  (dap-node-setup))

Meta Lisp

Here are packages that are useful across different Lisp and Scheme implementations:


(use-package lispy
  :hook ((emacs-lisp-mode . lispy-mode)
         (scheme-mode . lispy-mode)))

;; (use-package evil-lispy
;;   :hook ((lispy-mode . evil-lispy-mode)))

(use-package lispyville
  :hook ((lispy-mode . lispyville-mode))
  :config
  (lispyville-set-key-theme '(operators c-w additional
                              additional-movement slurp/barf-cp
                              prettify)))

Guix Packages


"emacs-lispy"
"emacs-lispyville"

Clojure


(use-package cider
  :mode "\\.clj[sc]?\\'"
  :config
  (evil-collection-cider-setup))

Guix Packages


"emacs-cider"

Common Lisp

Not currently doing any Common Lisp development so these packages are disabled for now.


(use-package sly
  :disabled
  :mode "\\.lisp\\'")

(use-package slime
  :disabled
  :mode "\\.lisp\\'")

Scheme


;; Include .sld library definition files
(use-package scheme-mode
  :straight nil
  :mode "\\.sld\\'")

TypeScript and JavaScript

Set up nvm so that we can manage Node versions


(use-package nvm
  :defer t)

Configure TypeScript and JavaScript language modes


(use-package typescript-mode
  :mode "\\.ts\\'"
  :config
  (setq typescript-indent-level 2))

(defun dw/set-js-indentation ()
  (setq js-indent-level 2)
  (setq evil-shift-width js-indent-level)
  (setq-default tab-width 2))

(use-package js2-mode
  :mode "\\.jsx?\\'"
  :config
  ;; Use js2-mode for Node scripts
  (add-to-list 'magic-mode-alist '("#!/usr/bin/env node" . js2-mode))

  ;; Don't use built-in syntax checking
  (setq js2-mode-show-strict-warnings nil)

  ;; Set up proper indentation in JavaScript and JSON files
  (add-hook 'js2-mode-hook #'dw/set-js-indentation)
  (add-hook 'json-mode-hook #'dw/set-js-indentation))


(use-package apheleia
  :config
  (apheleia-global-mode +1))

(use-package prettier-js
  ;; :hook ((js2-mode . prettier-js-mode)
  ;;        (typescript-mode . prettier-js-mode))
  :config
  (setq prettier-js-show-errors nil))

Guix Packages


"emacs-js2-mode"
"emacs-typescript-mode"
"emacs-apheleia"
"emacs-prettier"

C/C++


(use-package ccls
  :hook ((c-mode c++-mode objc-mode cuda-mode) .
         (lambda () (require 'ccls) (lsp))))

Guix Packages


"ccls"
"emacs-ccls"

Go


(use-package go-mode
  :hook (go-mode . lsp-deferred))

Guix Packages


"emacs-go-mode"

Rust


(use-package rust-mode
  :mode "\\.rs\\'"
  :init (setq rust-format-on-save t))

(use-package cargo
  :straight t
  :defer t)

Guix Packages


"emacs-rust-mode"

OCaml


"emacs-tuareg"

F#


(use-package fsharp-mode
  :mode ".fs[iylx]?\\'")

Emacs Lisp


(add-hook 'emacs-lisp-mode-hook #'flycheck-mode)

(use-package helpful
  :custom
  (counsel-describe-function-function #'helpful-callable)
  (counsel-describe-variable-function #'helpful-variable)
  :bind
  ([remap describe-function] . helpful-function)
  ([remap describe-symbol] . helpful-symbol)
  ([remap describe-variable] . helpful-variable)
  ([remap describe-command] . helpful-command)
  ([remap describe-key] . helpful-key))

(dw/leader-key-def
  "e"   '(:ignore t :which-key "eval")
  "eb"  '(eval-buffer :which-key "eval buffer"))

(dw/leader-key-def
  :keymaps '(visual)
  "er" '(eval-region :which-key "eval region"))

Guix Packages


"emacs-helpful"

Scheme


;; TODO: This causes issues for some reason.
;; :bind (:map geiser-mode-map
;;        ("TAB" . completion-at-point))

(use-package geiser
  :straight t
  :config
  ;; (setq geiser-default-implementation 'gambit)
  (setq geiser-default-implementation 'guile)
  (setq geiser-active-implementations '(gambit guile))
  (setq geiser-repl-default-port 44555) ; For Gambit Scheme
  (setq geiser-implementations-alist '(((regexp "\\.scm$") gambit)
                                       ((regexp "\\.sld") gambit))))

Guix Packages


"emacs-geiser"

Zig


(use-package zig-mode
  :after lsp-mode
  :straight t
  :config
  (require 'lsp)
  (add-to-list 'lsp-language-id-configuration '(zig-mode . "zig"))
  (lsp-register-client
    (make-lsp-client
      :new-connection (lsp-stdio-connection "~/Projects/Code/zls/zig-cache/bin/zls")
      :major-modes '(zig-mode)
      :server-id 'zls)))

Markdown


(use-package markdown-mode
  :straight t
  :mode "\\.md\\'"
  :config
  (setq markdown-command "marked")
  (defun dw/set-markdown-header-font-sizes ()
    (dolist (face '((markdown-header-face-1 . 1.2)
                    (markdown-header-face-2 . 1.1)
                    (markdown-header-face-3 . 1.0)
                    (markdown-header-face-4 . 1.0)
                    (markdown-header-face-5 . 1.0)))
      (set-face-attribute (car face) nil :weight 'normal :height (cdr face))))

  (defun dw/markdown-mode-hook ()
    (dw/set-markdown-header-font-sizes))

  (add-hook 'markdown-mode-hook 'dw/markdown-mode-hook))

Guix Packages


"emacs-markdown-mode"

HTML


(use-package web-mode
  :mode "(\\.\\(html?\\|ejs\\|tsx\\|jsx\\)\\'"
  :config
  (setq-default web-mode-code-indent-offset 2)
  (setq-default web-mode-markup-indent-offset 2)
  (setq-default web-mode-attribute-indent-offset 2))

;; 1. Start the server with `httpd-start'
;; 2. Use `impatient-mode' on any buffer
(use-package impatient-mode
  :straight t)

(use-package skewer-mode
  :straight t)

Guix Packages


"emacs-web-mode"

YAML


(use-package yaml-mode
  :mode "\\.ya?ml\\'")

Guix Packages


"emacs-yaml-mode"

ADL


(use-package adl-mode
  :straight nil
  :mode "\\.adl\\'"
  :hook (adl-mode . lsp-deferred))

Compilation

Set up the compile package and ensure that compilation output automatically scrolls.


(use-package compile
  :straight nil
  :custom
  (compilation-scroll-output t))

(defun auto-recompile-buffer ()
  (interactive)
  (if (member #'recompile after-save-hook)
      (remove-hook 'after-save-hook #'recompile t)
    (add-hook 'after-save-hook #'recompile nil t)))

Productivity

Syntax checking with Flycheck


(use-package flycheck
  :defer t
  :hook (lsp-mode . flycheck-mode))

Guix Packages


"emacs-flycheck"

Snippets


(use-package yasnippet
  :hook (prog-mode . yas-minor-mode)
  :config
  (yas-reload-all))

Guix Packages


"emacs-yasnippet"
"emacs-yasnippet-snippets"
;; "emacs-ivy-yasnippet"  ;; Not in config yet

Smart Parens


(use-package smartparens
  :hook (prog-mode . smartparens-mode))

Guix Packages


"emacs-smartparens"

Rainbow Delimiters


(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

Guix Packages


"emacs-rainbow-delimiters"

Rainbow Mode

Sets the background of HTML color strings in buffers to be the color mentioned.


(use-package rainbow-mode
  :defer t
  :hook (org-mode
         emacs-lisp-mode
         web-mode
         typescript-mode
         js2-mode))

Guix Packages


"emacs-rainbow-mode"

Tools

Debbugs (mainly for Guix)


;; TODO: Figure out how to query for 'done' bugs
(defun dw/debbugs-guix-patches ()
  (interactive)
  (debbugs-gnu '("serious" "important" "normal") "guix-patches" nil t))

Reference

HTTP


(use-package know-your-http-well
  :defer t)

Guix Packages


"emacs-know-your-http-well"

Game Development

Substratic Forge


(use-package substratic-forge
  :straight nil
  :if (file-exists-p "~/Projects/Code/crash-the-stack/lib/github.com/substratic/forge/@/")
  :load-path "~/Projects/Code/crash-the-stack/lib/github.com/substratic/forge/@/"
  :bind (:map substratic-forge-mode-map
         ("C-c C-m" . substratic-reload-module)))

Writing

Darkroom for distraction-free writing


(use-package darkroom
  :commands darkroom-mode
  :config
  (setq darkroom-text-scale-increase 0))

(defun dw/enter-focus-mode ()
  (interactive)
  (darkroom-mode 1)
  (display-line-numbers-mode 0))

(defun dw/leave-focus-mode ()
  (interactive)
  (darkroom-mode 0)
  (display-line-numbers-mode 1))

(defun dw/toggle-focus-mode ()
  (interactive)
  (if (symbol-value darkroom-mode)
    (dw/leave-focus-mode)
    (dw/enter-focus-mode)))

(dw/leader-key-def
  "tf" '(dw/toggle-focus-mode :which-key "focus mode"))

Guix Packages


"emacs-darkroom"

Previewing Info files

I'm experimenting with generating Texinfo from Org Mode files and I need a way to quickly preview the resulting .info files. This auto-mode-alist entry automatically previews an .info file when visited with find-file:


(add-to-list 'auto-mode-alist '("\\.info\\'" . Info-on-current-buffer))

Streaming


(use-package posframe)

(use-package command-log-mode
  :straight t
  :after posframe)

(setq dw/command-window-frame nil)

(defun dw/toggle-command-window ()
  (interactive)
  (if dw/command-window-frame
      (progn
        (posframe-delete-frame clm/command-log-buffer)
        (setq dw/command-window-frame nil))
      (progn
        (global-command-log-mode t)
        (with-current-buffer
          (setq clm/command-log-buffer
                (get-buffer-create " *command-log*"))
          (text-scale-set -1))
        (setq dw/command-window-frame
          (posframe-show
            clm/command-log-buffer
            :position `(,(- (x-display-pixel-width) 650) . 50)
            :width 35
            :height 5
            :min-width 35
            :min-height 5
            :internal-border-width 2
            :internal-border-color "#c792ea"
            :override-parameters '((parent-frame . nil)))))))

(use-package keycast
  :config
  ;; This works with doom-modeline, inspired by this comment:
  ;; https://github.com/tarsius/keycast/issues/7#issuecomment-627604064
  (define-minor-mode keycast-mode
    "Show current command and its key binding in the mode line."
    :global t
    (if keycast-mode
        (add-hook 'pre-command-hook 'keycast-mode-line-update t)
        (remove-hook 'pre-command-hook 'keycast-mode-line-update)))

  (add-to-list 'global-mode-string '("" mode-line-keycast " ")))

(dw/leader-key-def
 "tc" 'dw/toggle-command-window)

Guix Packages


"emacs-posframe"
"emacs-keycast"

obs-websocket

I use the obs-websocket plugin for OBS Studio to enable automation of scene transitions, etc using Sacha Chua's excellent obs-websocket-el package. This enables me to control the how flow of video recording and streaming from within Emacs!


(use-package obs-websocket
  :after hydra
  ;; :straight '(obs-websocket :host github :repo "sachac/obs-websocket-el")
  :config
  (defhydra dw/stream-keys (:exit t)
    "Stream Commands"
    ("c" (obs-websocket-connect) "Connect")
    ("s" (obs-websocket-send "SetCurrentScene" :scene-name "Screen") "Screen")
    ("w" (obs-websocket-send "SetCurrentScene" :scene-name "Webcam") "Webcam")
    ("p" (obs-websocket-send "SetCurrentScene" :scene-name "Sponsors") "Sponsors")
    ("Ss" (obs-websocket-send "StartStreaming") "Start Stream")
    ("Se" (obs-websocket-send "StopStreaming") "End Stream"))

  ;; This is Super-s (for now)
  (global-set-key (kbd "s-s") #'dw/stream-keys/body))

Guix Packages


"emacs-obs-websocket-el"

Applications

Binding Prefix


(dw/leader-key-def
  "a"  '(:ignore t :which-key "apps"))

Mail

My mail configuration is stored in Mail.org. We merely require it here to have it loaded in the main Emacs configuration.


;; Only fetch mail on zerocool
(setq dw/mail-enabled (member system-name '("zerocool" "acidburn")))
(setq dw/mu4e-inbox-query nil)
(when dw/mail-enabled
  (require 'dw-mail))

Guix Packages

emacs-mu4e-alert pulls in mu so we don't need to specify it here also.


"emacs-mu4e-alert"

Calendar

calfw is a gorgeous calendar UI that is able to show all of my scheduled Org Agenda items.


(use-package calfw
  :disabled
  :commands cfw:open-org-calendar
  :config
  (setq cfw:fchar-junction ?╋
        cfw:fchar-vertical-line ?┃
        cfw:fchar-horizontal-line ?━
        cfw:fchar-left-junction ?┣
        cfw:fchar-right-junction ?┫
        cfw:fchar-top-junction ?┯
        cfw:fchar-top-left-corner ?┏
        cfw:fchar-top-right-corner ?┓)

  (use-package calfw-org
    :config
    (setq cfw:org-agenda-schedule-args '(:timestamp))))

(dw/leader-key-def
  "cc"  '(cfw:open-org-calendar :which-key "calendar"))

Guix Packages


"emacs-calfw"

Finance


(use-package ledger-mode
  :mode "\\.lgr\\'"
  :bind (:map ledger-mode-map
              ("TAB" . completion-at-point))
  :custom
  (ledger-reports '(("bal" "%(binary) -f %(ledger-file) bal")
                    ("bal this quarter" "%(binary) -f %(ledger-file) --period \"this quarter\" bal")
                    ("bal last quarter" "%(binary) -f %(ledger-file) --period \"last quarter\" bal")
                    ("reg" "%(binary) -f %(ledger-file) reg")
                    ("payee" "%(binary) -f %(ledger-file) reg @%(payee)")
                    ("account" "%(binary) -f %(ledger-file) reg %(account)"))))

(use-package hledger-mode
  :straight t
  :bind (:map hledger-mode-map
              ("TAB" . completion-at-point)))

Guix Packages


"ledger"
;; "hledger"
"emacs-ledger-mode"

eshell

Configuration


(defun read-file (file-path)
  (with-temp-buffer
    (insert-file-contents file-path)
    (buffer-string)))

(defun dw/get-current-package-version ()
  (interactive)
  (let ((package-json-file (concat (eshell/pwd) "/package.json")))
    (when (file-exists-p package-json-file)
      (let* ((package-json-contents (read-file package-json-file))
             (package-json (ignore-errors (json-parse-string package-json-contents))))
        (when package-json
          (ignore-errors (gethash "version" package-json)))))))

(defun dw/map-line-to-status-char (line)
  (cond ((string-match "^?\\? " line) "?")))

(defun dw/get-git-status-prompt ()
  (let ((status-lines (cdr (process-lines "git" "status" "--porcelain" "-b"))))
    (seq-uniq (seq-filter 'identity (mapcar 'dw/map-line-to-status-char status-lines)))))

(defun dw/get-prompt-path ()
  (let* ((current-path (eshell/pwd))
         (git-output (shell-command-to-string "git rev-parse --show-toplevel"))
         (has-path (not (string-match "^fatal" git-output))))
    (if (not has-path)
      (abbreviate-file-name current-path)
      (string-remove-prefix (file-name-directory git-output) current-path))))

;; This prompt function mostly replicates my custom zsh prompt setup
;; that is powered by github.com/denysdovhan/spaceship-prompt.
(defun dw/eshell-prompt ()
  (let ((current-branch (magit-get-current-branch))
        (package-version (dw/get-current-package-version)))
    (concat
     "\n"
     (propertize (system-name) 'face `(:foreground "#62aeed"))
     (propertize " ॐ " 'face `(:foreground "white"))
     (propertize (dw/get-prompt-path) 'face `(:foreground "#82cfd3"))
     (when current-branch
       (concat
        (propertize " • " 'face `(:foreground "white"))
        (propertize (concat " " current-branch) 'face `(:foreground "#c475f0"))))
     (when package-version
       (concat
        (propertize " @ " 'face `(:foreground "white"))
        (propertize package-version 'face `(:foreground "#e8a206"))))
     (propertize " • " 'face `(:foreground "white"))
     (propertize (format-time-string "%I:%M:%S %p") 'face `(:foreground "#5a5b7f"))
     (if (= (user-uid) 0)
         (propertize "\n#" 'face `(:foreground "red2"))
       (propertize "\nλ" 'face `(:foreground "#aece4a")))
     (propertize " " 'face `(:foreground "white")))))

(unless dw/is-termux
  (add-hook 'eshell-banner-load-hook
            (lambda ()
               (setq eshell-banner-message
                     (concat "\n" (propertize " " 'display (create-image "~/.dotfiles/.emacs.d/images/flux_banner.png" 'png nil :scale 0.2 :align-to "center")) "\n\n")))))

(defun dw/eshell-configure ()
  (require 'evil-collection-eshell)
  (evil-collection-eshell-setup)

  (use-package xterm-color)

  (push 'eshell-tramp eshell-modules-list)
  (push 'xterm-color-filter eshell-preoutput-filter-functions)
  (delq 'eshell-handle-ansi-color eshell-output-filter-functions)

  ;; Save command history when commands are entered
  (add-hook 'eshell-pre-command-hook 'eshell-save-some-history)

  (add-hook 'eshell-before-prompt-hook
            (lambda ()
              (setq xterm-color-preserve-properties t)))

  ;; Truncate buffer for performance
  (add-to-list 'eshell-output-filter-functions 'eshell-truncate-buffer)

  ;; We want to use xterm-256color when running interactive commands
  ;; in eshell but not during other times when we might be launching
  ;; a shell command to gather its output.
  (add-hook 'eshell-pre-command-hook
            (lambda () (setenv "TERM" "xterm-256color")))
  (add-hook 'eshell-post-command-hook
            (lambda () (setenv "TERM" "dumb")))

  ;; Use completion-at-point to provide completions in eshell
  (define-key eshell-mode-map (kbd "<tab>") 'completion-at-point)

  ;; Initialize the shell history
  (eshell-hist-initialize)

  (evil-define-key '(normal insert visual) eshell-mode-map (kbd "C-r") 'consult-history)
  (evil-define-key '(normal insert visual) eshell-mode-map (kbd "<home>") 'eshell-bol)
  (evil-normalize-keymaps)

  (setenv "PAGER" "cat")

  (setq eshell-prompt-function      'dw/eshell-prompt
        eshell-prompt-regexp        "^λ "
        eshell-history-size         10000
        eshell-buffer-maximum-lines 10000
        eshell-hist-ignoredups t
        eshell-highlight-prompt t
        eshell-scroll-to-bottom-on-input t
        eshell-prefer-lisp-functions nil))

(use-package eshell
  :hook (eshell-first-time-mode . dw/eshell-configure)
  :init
  (setq eshell-directory-name "~/.dotfiles/.emacs.d/eshell/")
        eshell-aliases-file (expand-file-name "~/.dotfiles/.emacs.d/eshell/alias"))

(use-package eshell-z
  :hook ((eshell-mode . (lambda () (require 'eshell-z)))
         (eshell-z-change-dir .  (lambda () (eshell/pushd (eshell/pwd))))))

(use-package exec-path-from-shell
  :init
  (setq exec-path-from-shell-check-startup-files nil)
  :config
  (when (memq window-system '(mac ns x))
    (exec-path-from-shell-initialize)))

(dw/leader-key-def
  "SPC" 'eshell)

Guix Packages


"emacs-eshell-z"
"emacs-esh-autosuggest"
"emacs-xterm-color"
"emacs-exec-path-from-shell"

Shell Commands

Custom eshell commands will go here.

Visual Commands


(with-eval-after-load 'esh-opt
  (setq eshell-destroy-buffer-when-process-dies t)
  (setq eshell-visual-commands '("htop" "zsh" "vim")))

Better Colors


;; (use-package eterm-256color
;;   :hook (term-mode . eterm-256color-mode))

Fish Completion

This enhances eshell's completions with those that Fish is capable of and also falls back to any additional completions that are configured for Bash on the system. The primary benefit here (for me) is getting completion for commits and branches in git commands.


(use-package fish-completion
  :hook (eshell-mode . fish-completion-mode))

Guix Packages


"emacs-fish-completion"

Command Highlighting


(use-package eshell-syntax-highlighting
  :after esh-mode
  :config
  (eshell-syntax-highlighting-global-mode +1))

Guix Packages


"emacs-eshell-syntax-highlighting"

History Autocompletion


(use-package esh-autosuggest
  :hook (eshell-mode . esh-autosuggest-mode)
  :config
  (setq esh-autosuggest-delay 0.5)
  (set-face-foreground 'company-preview-common "#4b5668")
  (set-face-background 'company-preview nil))

Toggling Eshell

eshell-toggle allows me to toggle an Eshell window below the current buffer for the path (or project path) of the buffer.


(use-package eshell-toggle
  :bind ("C-M-'" . eshell-toggle)
  :custom
  (eshell-toggle-size-fraction 3)
  (eshell-toggle-use-projectile-root t)
  (eshell-toggle-run-command nil))

Guix Packages


"emacs-eshell-toggle"

vterm

vterm enables the use of fully-fledged terminal applications within Emacs so that I don't need an external terminal emulator.


(use-package vterm
  :commands vterm
  :config
  (setq vterm-max-scrollback 10000))

Guix Packages


"emacs-vterm"

multi-term

Since I switched over to eshell I don't use multi-term as much anymore. Some helpful configuration tips can be found here.


(use-package multi-term
  :commands multi-term-next
  :config
  (setq term-buffer-maximum-size 10000)
  (setq term-scroll-to-bottom-on-output t)
  (add-hook 'term-mode-hook
      (lambda ()
        (add-to-list 'term-bind-key-alist '("M-[" . multi-term-prev))
        (add-to-list 'term-bind-key-alist '("M-]" . multi-term-next)))))

(dw/leader-key-def
  "C-SPC" 'multi-term-next)

Guix Packages


"emacs-multi-term"

ediff


;; Don't let ediff break EXWM, keep it in one frame
(setq ediff-diff-options "-w"
      ediff-split-window-function 'split-window-horizontally
      ediff-window-setup-function 'ediff-setup-windows-plain)

Chat

Tracking


(use-package tracking
  :defer t
  :config
  (setq tracking-faces-priorities '(all-the-icons-pink
                                    all-the-icons-lgreen
                                    all-the-icons-lblue))
  (setq tracking-frame-behavior nil))

Guix Packages


"emacs-tracking"

Telegram


;; Add faces for specific people in the modeline.  There must
;; be a better way to do this.
(defun dw/around-tracking-add-buffer (original-func buffer &optional faces)
  (let* ((name (buffer-name buffer))
         (face (cond ((s-contains? "Maria" name) '(all-the-icons-pink))
                     ((s-contains? "Alex " name) '(all-the-icons-lgreen))
                     ((s-contains? "Steve" name) '(all-the-icons-lblue))))
         (result (apply original-func buffer (list face))))
    (dw/update-polybar-telegram)
    result))

(defun dw/after-tracking-remove-buffer (buffer)
  (dw/update-polybar-telegram))

(advice-add 'tracking-add-buffer :around #'dw/around-tracking-add-buffer)
(advice-add 'tracking-remove-buffer :after #'dw/after-tracking-remove-buffer)
(advice-remove 'tracking-remove-buffer #'dw/around-tracking-remove-buffer)

;; Advise exwm-workspace-switch so that we can more reliably clear tracking buffers
;; NOTE: This is a hack and I hate it.  It'd be great to find a better solution.
(defun dw/before-exwm-workspace-switch (frame-or-index &optional force)
  (when (fboundp 'tracking-remove-visible-buffers)
    (when (eq exwm-workspace-current-index 0)
      (tracking-remove-visible-buffers))))

(advice-add 'exwm-workspace-switch :before #'dw/before-exwm-workspace-switch)

(use-package telega
  :commands telega
  :config
  (setq telega-user-use-avatars nil
        telega-use-tracking-for '(any pin unread)
        telega-chat-use-markdown-formatting t
        telega-emoji-use-images t
        telega-completing-read-function #'ivy-completing-read
        telega-msg-rainbow-title nil
        telega-chat-fill-column 75))

Guix Packages


"emacs-telega"

Discord

elcord makes it possible to notify Discord when Emacs is "playing" using the Game Activity feature. Since I run the Discord Flatpak from Flathub, I have to expose the socket file via symbolic link like so:


ln -sf {app/com.discordapp.Discord,$XDG_RUNTIME_DIR}/discord-ipc-0


(use-package elcord
  :straight t
  :disabled dw/is-termux
  :custom
  (elcord-display-buffer-details nil)
  :config
  (elcord-mode))

ERC

ERC is the big kahuna of Emacs IRC clients. At first I thought it was too bulky, but after using circe and rcirc I started to appreciate some of the features it provides. The "static center" fill mode is really awesome.

  • Configuration
    
    (defun dw/on-erc-track-list-changed ()
      (dolist (buffer erc-modified-channels-alist)
        (tracking-add-buffer (car buffer))))
    
    (use-package erc-hl-nicks
      :after erc)
    
    (use-package erc-image
      :after erc)
    
    (use-package erc
      :commands erc
      :hook (erc-track-list-changed . dw/on-erc-track-list-changed)
      :config
      (setq
          erc-nick "daviwil"
          erc-user-full-name "David Wilson"
          erc-prompt-for-nickserv-password nil
          erc-auto-query 'bury
          erc-join-buffer 'bury
          erc-interpret-mirc-color t
          erc-rename-buffers t
          erc-lurker-hide-list '("JOIN" "PART" "QUIT")
          erc-track-exclude-types '("JOIN" "NICK" "QUIT" "MODE")
          erc-track-enable-keybindings nil
          erc-track-visibility nil ; Only use the selected frame for visibility
          erc-fill-column 80
          erc-fill-function 'erc-fill-static
          erc-fill-static-center 20
          erc-track-exclude '("#twitter_daviwil")
          erc-autojoin-channels-alist '(("freenode.net" "#emacs" "#guix"))
          erc-quit-reason (lambda (s) (or s "Fading out..."))
          erc-modules
          '(autoaway autojoin button completion fill irccontrols keep-place
              list match menu move-to-prompt netsplit networks noncommands
              readonly ring stamp track hl-nicks))
    
      (add-hook 'erc-join-hook 'bitlbee-identify)
      (defun bitlbee-identify ()
        "If we're on the bitlbee server, send the identify command to the &bitlbee channel."
        (when (and (string= "127.0.0.1" erc-session-server)
                   (string= "&bitlbee" (buffer-name)))
          (erc-message "PRIVMSG" (format "%s identify %s"
                                         (erc-default-target)
                                         (password-store-get "IRC/Bitlbee"))))))
    
    (defun dw/connect-irc ()
      (interactive)
      (erc-tls
         :server "chat.freenode.net" :port 7000
         :nick "daviwil" :password (password-store-get "IRC/Freenode")))
      ;; (erc
      ;;    :server "127.0.0.1" :port 6667
      ;;    :nick "daviwil" :password (password-store-get "IRC/Bitlbee")))
    
    

    Guix Packages

    
    "emacs-erc-image"
    "emacs-erc-hl-nicks"
    
    
  • Bindings
    
    (dw/ctrl-c-keys
      "c"  '(:ignore t :which-key "chat")
      "cb" 'erc-switch-to-buffer
      "cc" 'dw/connect-irc
      "ca" 'erc-track-switch-buffer)
    
    
  • Reference

Matrix


(use-package matrix-client)

Guix Packages


"emacs-matrix-client"

Mastodon


(use-package mastodon
  :defer t
  :config
  (setq mastodon-instance-url "https://mastodon.social"))

Guix Packages


"emacs-mastodon"

RSS with Elfeed

Elfeed looks like a great RSS feed reader. Not using it much yet, but definitely looking forward to using it to keep track of a few different blogs I follow using Twitter. Also seems to be great for following subreddits like /r/Emacs.


(use-package elfeed
  :commands elfeed
  :config
  (setq elfeed-feeds
    '("https://nullprogram.com/feed/"
      "https://ambrevar.xyz/atom.xml"
      "https://guix.gnu.org/feeds/blog.atom"
      "https://valdyas.org/fading/feed/"
      "https://www.reddit.com/r/emacs/.rss")))

Guix Packages


"emacs-elfeed"

Media

EMMS


(use-package emms
  :commands emms
  :config
  (require 'emms-setup)
  (emms-standard)
  (emms-default-players)
  (emms-mode-line-disable)
  (setq emms-source-file-default-directory "~/Music/")
  (dw/leader-key-def
    "am"  '(:ignore t :which-key "media")
    "amp" '(emms-pause :which-key "play / pause")
    "amf" '(emms-play-file :which-key "play file")))

Guix Packages


"emacs-emms"

Gemini


(use-package elpher)

Guix Packages


"emacs-elpher"

System Utilities

Guix


(use-package guix
  :defer t)

(dw/leader-key-def
  "G"  '(:ignore t :which-key "Guix")
  "Gg" '(guix :which-key "Guix")
  "Gi" '(guix-installed-user-packages :which-key "user packages")
  "GI" '(guix-installed-system-packages :which-key "system packages")
  "Gp" '(guix-packages-by-name :which-key "search packages")
  "GP" '(guix-pull :which-key "pull"))

Guix Packages


"emacs-guix"

Daemons


(use-package daemons
  :commands daemons)

Guix Packages


"emacs-daemons"

PulseAudio


(use-package pulseaudio-control
  :commands pulseaudio-control-select-sink-by-name
  :config
  (setq pulseaudio-control-pactl-path "/run/current-system/profile/bin/pactl"))

Guix Packages


"emacs-pulseaudio-control"

Bluetooth


(defun dw/bluetooth-connect-q30 ()
  (interactive)
  (start-process-shell-command "bluetoothctl" nil "bluetoothctl -- connect 11:14:00:00:1E:1A"))

(defun dw/bluetooth-connect-qc35 ()
  (interactive)
  (start-process-shell-command "bluetoothctl" nil "bluetoothctl -- connect 04:52:C7:5E:5C:A8"))

(defun dw/bluetooth-disconnect ()
  (interactive)
  (start-process-shell-command "bluetoothctl" nil "bluetoothctl -- disconnect"))

Proced


(use-package proced
  :commands proced
  :config
  (setq proced-auto-update-interval 1)
  (add-hook 'proced-mode-hook
            (lambda ()
              (proced-toggle-auto-update 1))))

Docker


(use-package docker
  :commands docker)

(use-package docker-tramp
  :defer t
  :after docker)

Guix Packages


"emacs-docker"
"emacs-docker-tramp"
"emacs-dockerfile-mode"

Runtime Performance

Dial the GC threshold back down so that garbage collection happens more frequently but in less time.


;; Make gc pauses faster by decreasing the threshold.
(setq gc-cons-threshold (* 2 1000 1000))

Emacs Profile

.config/guix/manifests/emacs.scm:


(specifications->manifest
 '(;;"emacs"
   "emacs-native-comp"
   <<packages>>
))

Inspiration

Awesome Emacs has a good list of packages and themes to check out.

Other dotfiles repos and blog posts for inspiration: