19:12 <alternateved> couple of helpful settings: 19:12 <alternateved> (completions-format ‘one-column) 19:13 <alternateved> (completions-max-height 20) 19:13 <alternateved> (completion-auto-select ‘second-tab)
Welcome to the latest iteration of my Emacs configuration! I’m not sure how many times I’ve rewritten my Emacs configuration over the years (it’s probably less than you think), but I’m approaching this one with some specific goals and use cases in mind.
I have specific goals for this configuration which I hope will serve some personal use cases and also inspire other people to experiment with their own ideas:
- The base configuration is terminal-first, meaning that everything I add there must provide a good experience for Emacs in the terminal.
init.el
itself should have no external package dependencies (if possible) and I should be able to drop it on any machine usingcurl
and have a comfortable environment ready to go immediately.- Additional functionality will be introduced using optional configuration modules which are only loaded on specific machines.
- Use built-in Emacs functionality as much as I can, only pull in community packages for critical workflows.
- Avoid special convenience key bindings; stay as close to “vanilla” as I can.
This particular set of goals may seem surprising, so I’d like to provide some rationale. I’m not attempting this because I think it’s somehow better to be minimalistic. I am trying to address specific use cases that I have instead of trying to come up with a generic configuration structure. What I do here may not be right for you!
I’ve described in a few previous live streams how I feel that it’s important I understand fundamental Emacs functionality and key bindings so that I can teach them effectively. I really want to give the best advice to someone on what options they have available for their workflow and I can’t do that if I don’t understand the tools that Emacs provides by default.
Also, I think there is some value in writing your own Emacs Lisp code for certain things that may be provided by community packages. Why depend on a small package with configuration options to suit many users when you could just write something similar for yourself that is tailored specifically to your needs?
There is probably more to say here that I haven’t thought of yet; please feel free to ask me any questions you have on IRC, the forum, or via email (my first name at systemcrafters.net).
A short list of features I’m trying to stick with this time before reaching for community packages:
- Use
icomplete-vertical-mode
orfido-vertical-mode
instead of Vertico. Vertico is amazing, but I want to know what the actual pain points of the built-in solutions are before I resort to using something that is not built in. - Get used to the built-in
completion-styles
before I try pulling inorderless
.orderless
is excellent but I honestly have not experimented enough with the built-in styles to see if I can get around well enough with them.
p
- Use
vc-mode
instead of Magit as long as I can.vc-mode
is nowhere near as polished as Magit, by any means. However, I should be able to do many of the same operations I accomplish with Magit (aside from interactive rebase?). I want to test the limits ofvc-mode
before I pull in Magit, and even then, I may only use Magit for specific tasks thatvc-mode
is not good at. - Try to avoid using
consult
for as long as I can.consult-line
is pretty useful, butisearch
is mostly fine and I haven’t even tried to configure it to be nicer yet. The one thing I will miss fromconsult
isconsult-ripgrep
, but I need to see if there’s a way to useripgrep
directly with Emacs’ existinggrep
functionality. - Try to avoid using “workspace” packages like
beframe
ortabspaces
. There must be other useful ways to manage buffers that don’t require forcing per-project isolation. There’s alreadyC-x p b
(project-switch-to-buffer
) that could take care of 80% of my needs. I also haven’t experimented enough withibuffer
for mass buffer management to clean up those that I don’t need any more in my current session. - No configuration helpers other than
use-package
since it’s now built in with Emacs 30. There are also new keymap macros like define-keymap which remove the need for packages likegeneral.el
, etc.
You may have realized that I am back to using a literate Emacs configuration with Org Babel. I’ve actually been planning to do this for a while but never got around to it until now.
I had a couple of reasons for abandoning my old literate configuration:
- Tangling config changes after syncing from my dotfiles can be a pain
- I wanted to treat my configuration as real code instead of code blocks in an Org file
After spending over a year with my Emacs config as plain .el
files, I feel like I’m missing the convenience of one big file that contains everything in one place. Sure, I can move around pretty quickly in my dotfiles repo using C-x p f
(project-find-file
), but half the time I forget which file some aspect of my Emacs config lives in.
Also, as many have noticed, the website where I publish my configuration has been down for almost as long as I haven’t been using a literate config due to DNS changes at SourceHut. I’ve been thinking about how I can get a website back up for my config again and tried a few different options, but going back to literate just feels like the most appropriate solution for now.
Lastly, I think a literate config is superior because it enables one to write real prose to explain their configuration and the rationale for why they do things a certain way. I wouldn’t have bothered to write this many paragraphs as a comment in an Emacs Lisp file. People can learn from reading about how you use Emacs, it’s a benefit to the community!
To set up this configuration on a new machine, there are two options:
- Download
emacs/init.el
into your home folder as.emacs
- OR Clone the whole
dotfiles
repo to~/.dotfiles
and run the following commands:
mkdir ~/emacs.d
ln -sf ~/.dotfiles/emacs/init.el ~/.emacs.d/
We don’t actually symlink the whole ~/.dotfiles/emacs
folder to ~/.emacs.d
to prevent Emacs from dumping runtime files and ELPA packages into the dotfiles repository!
The standard init.el
file is shared between all machines that use this configuration. It is intended that file can be copied directly onto a new machine to get a convenient working environment up and running quickly.
Additional functionality is loaded from module files which are defined in sections later in this document.
I’ve structured all of this in a very concise way because much of it doesn’t need explanation if you’ve been using Emacs for a while.
;; -*- lexical-binding: t; -*-
;;; This file is generated from the Emacs.org file in my dotfiles repository!
;;; ----- Basic Configuration -----
;; Core settings
(setq ;; Flash the UI instead of beeping
visible-bell t
;; Yes, this is Emacs
inhibit-startup-message t
;; Instruct auto-save-mode to save to the current file, not a backup file
auto-save-default nil
;; No backup files, please
make-backup-files nil
;; Make it easy to cycle through previous items in the mark ring
set-mark-command-repeat-pop t
;; Don't warn on large files
large-file-warning-threshold nil
;; Follow symlinks to VC-controlled files without warning
vc-follow-symlinks t
;; Don't warn on advice
ad-redefinition-action 'accept
;; Revert Dired and other buffers
global-auto-revert-non-file-buffers t
;; Silence compiler warnings as they can be pretty disruptive
native-comp-async-report-warnings-errors nil)
;; Core modes
(repeat-mode 1) ;; Enable repeating key maps
(menu-bar-mode 0) ;; Hide the menu bar
(tool-bar-mode 0) ;; Hide the tool bar
(savehist-mode 1) ;; Save minibuffer history
(scroll-bar-mode 0) ;; Hide the scroll bar
(xterm-mouse-mode 1) ;; Enable mouse events in terminal Emacs
(display-time-mode 1) ;; Display time in mode line / tab bar
(fido-vertical-mode 1) ;; Improved vertical minibuffer completions
(column-number-mode 1) ;; Show column number on mode line
(tab-bar-history-mode 1) ;; Remember previous tab window configurations
(auto-save-visited-mode 1) ;; Auto-save files at an interval
(global-visual-line-mode 1) ;; Visually wrap long lines in all buffers
(global-auto-revert-mode 1) ;; Refresh buffers with changed local files
;; Tabs to spaces
(setq-default indent-tabs-mode nil
tab-width 2)
;; Display line numbers in programming modes
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
;; Make icomplete slightly more convenient
(keymap-set icomplete-fido-mode-map "M-h" 'icomplete-fido-backward-updir)
(keymap-set icomplete-fido-mode-map "TAB" 'icomplete-force-complete)
;; Delete trailing whitespace before saving buffers
(add-hook 'before-save-hook 'delete-trailing-whitespace)
;; Move customization settings out of init.el
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (file-exists-p custom-file)
(load custom-file t))
;; Match completion substrings that may be out of order
(defun dw/override-fido-completion-styles ()
(setq-local completion-styles '(substring partial-completion emacs22)))
(add-hook 'icomplete-minibuffer-setup-hook 'dw/override-fido-completion-styles)
I often need to modify behavior based on the type of system Emacs is running on, especially if it’s running on Guix.
;;; ----- System Identification -----
(defvar dw/is-termux
(string-suffix-p "Android" (string-trim (shell-command-to-string "uname -a"))))
(defvar dw/current-distro (or (and (eq system-type 'gnu/linux)
(file-exists-p "/etc/os-release")
(with-temp-buffer
(insert-file-contents "/etc/os-release")
(search-forward-regexp "^ID=\"?\\(.*\\)\"?$")
(intern (or (match-string 1)
"unknown"))))
'unknown))
(defvar dw/is-guix-system (eql dw/current-distro 'guix))
Customize package management based on the type of system Emacs is running on.
;;; ----- Package Management -----
;; Automatically install packages (when not on Guix) but don't load
;; them until requested
(setq use-package-always-ensure (not dw/is-guix-system)
use-package-always-defer t)
This section is currently under development as I figure out the best pattern to use for providing customization “knobs” on the base configuration.
For now, the idea is that I provide variables and functions that can be called in machine-specific configuration files (named with the system-name
) to customize basic configuration details and load extension modules that are needed for each machine.
;;; ----- Configuration Management -----
(defvar dw/use-config-modules '()
"A list of module symbols to load once init.el is finished.")
(defvar dw/common-config-modules '(dw-auth
dw-irc
dw-present
dw-0x0
dw-writing
dw-workflow)
"Configuration modules most commonly used across my machines.")
;; Add configuration modules to load path
(add-to-list 'load-path '"~/.dotfiles/emacs/modules")
;; Load system-specific configuration
(let ((config-path
(format "~/.dotfiles/emacs/systems/%s.el" system-name)))
(if (file-exists-p config-path)
(load-file config-path)
(message "No per-system configuration found for %s!" system-name)))
I prefer to use terminals that support the full range of colors so that themes like ef-themes
can have an equivalent appearance to graphical Emacs.
However, setting a background color in an Emacs theme generally defeats any transparency settings of the terminals I’ve used so I’ve added the dw/clear-background-color
function to clear the background color after a theme gets applied.
I may switch to using a highly-customized modus-vivendi
here if I can figure out the right combination of colors to approximate the usual doom-palenight
theme that I use on System Crafters videos and streams.
;;; ----- Appearance -----
(defun dw/set-terminal-title (title)
(send-string-to-terminal (format "\e]0;%s\a" title)))
(defun dw/clear-background-color (&optional frame)
(interactive)
(or frame (setq frame (selected-frame)))
"unsets the background color in terminal mode"
(unless (display-graphic-p frame)
;; Set the terminal to a transparent version of the background color
(send-string-to-terminal
(format "\033]11;[90]%s\033\\"
(face-attribute 'default :background)))
(set-face-background 'default "unspecified-bg" frame)))
;; Clear the background color for transparent terminals
(unless (display-graphic-p)
(add-hook 'after-make-frame-functions 'dw/clear-background-color)
(add-hook 'window-setup-hook 'dw/clear-background-color)
(add-hook 'ef-themes-post-load-hook 'dw/clear-background-color))
(when (display-graphic-p)
(set-face-attribute 'default nil
:font "JetBrains Mono"
:weight 'normal
:height 140)
;; Set the fixed pitch face
(set-face-attribute 'fixed-pitch nil
:font "JetBrains Mono"
:weight 'normal
:height 140)
;; Set the variable pitch face
(set-face-attribute 'variable-pitch nil
:font "Iosevka Aile"
:height 120
:weight 'normal)
;; Make frames transparent
(set-frame-parameter (selected-frame) 'alpha-background 93)
(add-to-list 'default-frame-alist '(alpha-background . 93))
(set-frame-parameter (selected-frame) 'fullscreen 'maximized)
(add-to-list 'default-frame-alist '(fullscreen . maximized)))
(use-package modus-themes
:ensure nil
:demand t
:custom
(modus-themes-italic-constructs t)
(modus-themes-bold-constructs t)
(modus-themes-common-palette-overrides
`((bg-main "#292D3E")
(bg-active bg-main)
(fg-main "#EEFFFF")
(fg-active fg-main)
(fringe unspecified)
(border-mode-line-active unspecified)
(border-mode-line-inactive unspecified)
(fg-mode-line-active "#A6Accd")
(bg-mode-line-active "#232635")
(fg-mode-line-inactive "#676E95")
(bg-mode-line-inactive "#282c3d")
(bg-tab-bar "#242837")
(bg-tab-current bg-main)
(bg-tab-other bg-active)
(fg-prompt "#c792ea")
(bg-prompt unspecified)
(bg-hover-secondary "#676E95")
(bg-completion "#2f447f")
(fg-completion white)
(bg-region "#3C435E")
(fg-region white)
(fg-heading-0 "#82aaff")
(fg-heading-1 "#82aaff")
(fg-heading-2 "#c792ea")
(fg-heading-3 "#bb80b3")
(fg-heading-4 "#a1bfff")
(fg-prose-verbatim "#c3e88d")
(bg-prose-block-contents "#232635")
(fg-prose-block-delimiter "#676E95")
(bg-prose-block-delimiter bg-prose-block-contents)
(accent-1 "#79a8ff")
(keyword "#89DDFF")
(builtin "#82aaff")
(comment "#676E95")
(string "#c3e88d")
(fnname "#82aaff")
(type "#c792ea")
(variable "#ffcb6b")
(docstring "#8d92af")
(constant "#f78c6c")))
:init
(load-theme 'modus-vivendi-tinted t)
(add-hook 'modus-themes-after-load-theme-hook #'dw/clear-background-color))
;; Make vertical window separators look nicer in terminal Emacs
(set-display-table-slot standard-display-table 'vertical-border (make-glyph-code ?│))
;; Clean up the mode line
(setq-default mode-line-format
'("%e" " "
(:propertize
("" mode-line-mule-info mode-line-client mode-line-modified mode-line-remote))
mode-line-frame-identification
mode-line-buffer-identification
" "
mode-line-position
mode-line-format-right-align
" "
(project-mode-line project-mode-line-format)
" "
(vc-mode vc-mode)
" "
mode-line-modes
mode-line-misc-info
" ")
project-mode-line t
mode-line-buffer-identification '(" %b")
mode-line-position-column-line-format '(" %l:%c"))
Tweak the tab bar to remove some unnecessary elements and shift the global-mode-string
there.
;; Move global mode string to the tab-bar and hide tab close buttons
(setq tab-bar-close-button-show nil
tab-bar-separator " "
tab-bar-format '(tab-bar-format-menu-bar
tab-bar-format-tabs-groups
tab-bar-separator
tab-bar-format-align-right
tab-bar-format-global))
;; Turn on the tab-bar
(tab-bar-mode 1)
Time is relative, OK?
;; Customize time display
(setq display-time-load-average nil
display-time-format "%l:%M %p %b %d W%U"
display-time-world-time-format "%a, %d %b %I:%M %p %Z"
display-time-world-list
'(("Etc/UTC" "UTC")
("Europe/Athens" "Athens")
("America/Los_Angeles" "Seattle")
("America/Denver" "Denver")
("America/New_York" "New York")
("Pacific/Auckland" "Auckland")
("Asia/Shanghai" "Shanghai")
("Asia/Kolkata" "Hyderabad")))
Here’s another minimal implementation of a package I commonly use called popper.el. The goal here is to automatically place a specific set of buffers into a popup window at the bottom of the frame and make that window togglable with a key binding.
I don’t currently support buffer cycling like popper
does but I don’t think I used it that much to begin with.
;; ----- Special Buffers as Popup Window -----
(setq display-buffer-alist
'(("\\*\\(shell\\|.*term\\|.*eshell\\|help\\|compilation\\|Async Shell Command\\|Occur\\|xref\\).*\\*"
(display-buffer-reuse-window display-buffer-in-side-window)
(side . bottom) ; Popups go at the bottom
(slot . 0) ; Use the first slot at the bottom
(post-command-select-window . t) ; Select the window upon display
(window-height . 0.3)))) ; 30% of the frame height
(defun dw/toggle-popup-window ()
(interactive)
(if-let ((popup-window
(get-window-with-predicate
(lambda (window)
(eq (window-parameter window 'window-side)
'bottom)))))
;; Focus the window if it is not selected, otherwise close it
(if (eq popup-window (selected-window))
(delete-window popup-window)
(select-window popup-window))
;; Find the most recent buffer that matches the rule and show it
;; NOTE: This logic is somewhat risky because it makes the assumption
;; that the popup rule comes first in `display-buffer-alist'.
;; I chose to do this because maintaining a separate variable
;; for this rule meant I had to re-evaluate 2 different forms
;; to update my rule list.
(if-let ((popup-buffer
(seq-find (lambda (buffer)
(buffer-match-p (caar display-buffer-alist)
(buffer-name buffer)))
(if (project-current)
(project-buffers (project-current))
(buffer-list (selected-frame))))))
(display-buffer popup-buffer (cdar display-buffer-alist))
(message "No popup buffers found."))))
;; TODO: This binding may need to change
(keymap-global-set "C-c p" #'dw/toggle-popup-window)
(with-eval-after-load 'term
(keymap-set term-raw-map "C-c p" #'dw/toggle-popup-window))
Here are the most important Org Mode settings that enable me to edit files comfortably, especially my literate configuration files.
;;; ----- Essential Org Mode Configuration -----
(setq org-ellipsis " ▾"
org-startup-folded 'content
org-cycle-separator-lines 2
org-fontify-quote-and-verse-blocks t)
;; Indent org-mode buffers for readability
(add-hook 'org-mode-hook #'org-indent-mode)
;; Set up Org Babel languages
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)))
;; Use org-tempo
(use-package org-tempo
:ensure nil
:demand t
:config
(dolist (item '(("sh" . "src sh")
("el" . "src emacs-lisp")
("li" . "src lisp")
("sc" . "src scheme")
("ts" . "src typescript")
("py" . "src python")
("yaml" . "src yaml")
("json" . "src json")
("einit" . "src emacs-lisp :tangle emacs/init.el")
("emodule" . "src emacs-lisp :tangle emacs/modules/dw-MODULE.el")))
(add-to-list 'org-structure-template-alist item)))
I previously used visual-fill-column-mode
for this functionality but decided to write my own minimal implementation so that I could avoid installing a MELPA package.
Works pretty well, but I’m not fully convinced this needs to be in init.el
. It certainly does make the writing experience more pleasant but may not be critical for minimal config deployments.
;;; ----- Document Centering -----
(defvar center-document-desired-width 90
"The desired width of a document centered in the window.")
(defun center-document--adjust-margins ()
;; Reset margins first before recalculating
(set-window-parameter nil 'min-margins nil)
(set-window-margins nil nil)
;; Adjust margins if the mode is on
(when center-document-mode
(let ((margin-width (max 0
(truncate
(/ (- (window-width)
center-document-desired-width)
2.0)))))
(when (> margin-width 0)
(set-window-parameter nil 'min-margins '(0 . 0))
(set-window-margins nil margin-width margin-width)))))
(define-minor-mode center-document-mode
"Toggle centered text layout in the current buffer."
:lighter " Centered"
:group 'editing
(if center-document-mode
(add-hook 'window-configuration-change-hook #'center-document--adjust-margins 'append 'local)
(remove-hook 'window-configuration-change-hook #'center-document--adjust-margins 'local))
(center-document--adjust-margins))
(add-hook 'org-mode-hook #'center-document-mode)
This is a configuration for Emacs’ built in shells, term
/ ansi-term
and eshell
.
;; Coming soon.
Dired doesn’t need much configuration, but the following just ensures that Dired buffers are organized in a way that makes sense to me, are free from unneeded information (at first), and doesn’t leave a ton of buffers open as I move around.
I also add a binding for b
to dired-up-directory
because it seems very strange to me that they have f
bound to dired-find-file
without b
moving in the “opposite” direction.
;;; ----- Dired -----
(defun dw/dired-mode-hook ()
(interactive)
(dired-hide-details-mode 1)
(hl-line-mode 1))
(use-package dired
:ensure nil
:bind (:map dired-mode-map
("b" . dired-up-directory))
:config
(setq dired-listing-switches "-alv --group-directories-first"
dired-omit-files "^\\.[^.].*"
dired-omit-verbose nil
dired-dwim-target 'dired-dwim-target-next
dired-hide-details-hide-symlink-targets nil
dired-kill-when-opening-new-dired-buffer t
delete-by-moving-to-trash t)
(add-hook 'dired-mode-hook #'dw/dired-mode-hook))
The built-in proced
package is quite useful for producing a list of all running processes. It’s basically like having top
built in to Emacs.
This configuration makes the proced
buffer update automatically on an interval so that you can watch process activity as it changes.
(use-package proced
:ensure nil
:config
(setq proced-auto-update-interval 1)
(add-hook 'proced-mode-hook
(lambda ()
(proced-toggle-auto-update 1))))
I search through files a lot. Here are some tweaks to make that experience a bit faster and more convenient:
;; Make sure ripgrep is used everywhere
(setq xref-search-program 'ripgrep
grep-command "rg -nS --noheading")
At the end of init.el
, we finalize any configuration settings that may have been applied at the per-system level. This includes loading any configuration modules that are requested.
;;; ----- Finalization
;; Load requested configuration modules
(dolist (module dw/use-config-modules)
(require module))
The following sections contain optional modules that will be loaded on a per-system basis depending on whether the specified features are needed.
I use Denote as my primary tool for writing in Emacs. It enables me to write whatever I need very efficiently without thinking too much about where the information belongs.
I also use it as the core for my personal productivity system which revolves largely around Denote-managed Org Mode notes with special file-level tags.
;; -*- lexical-binding: t; -*-
(use-package denote
:demand t
:bind (("C-c n l" . denote-link-or-create)
("C-c n o" . denote-open-or-create)
("C-c n r" . denote-rename-file-using-front-matter))
:custom
(denote-directory "~/Notes")
(denote-rename-buffer-format "Denote: %t (%k)")
(denote-infer-keywords nil)
(denote-known-keywords
'("pra" "prb" "prc"
"ply" "plm" "plw"
"kt" "ke" "kp" "kl" "ka" "kap"
"kcp" "kca" "kcc"
"kra" "krb" "krv"
"rn"))
:config
(require 'denote-rename-buffer)
(require 'denote-org-extras)
;; Rename buffers with the note name
(denote-rename-buffer-mode 1)
;; Buttonize all denote links in text buffers
(add-hook 'text-mode-hook #'denote-fontify-links-mode-maybe))
(defun dw/setup-markdown-mode ()
(center-document-mode 1)
(display-line-numbers-mode 0))
(use-package markdown-mode
:config
(setq markdown-command "marked")
(add-hook 'markdown-mode-hook #'dw/setup-markdown-mode)
(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))))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(use-package howm
:init
(setq howm-directory "~/Howm")
(setq howm-home-directory howm-directory)
;(setq howm-file-name-format "%Y-%m-%d-%H%M%S.org")
;(setq howm-view-title-header "*")
;(setq howm-dtime-format "<%Y-%m-%d %a %H:%M>")
(setq howm-file-name-format "%Y-%m-%d-%H%M%S.md")
(setq howm-view-title-header "#")
(setq howm-prefix (kbd "C-c ;"))
:bind*
;; Conveniently open the Howm menu with "C-c ; ;".
("C-c ; ;" . howm-menu))
(provide 'dw-writing)
;; -*- lexical-binding: t; -*-
;;; ----- TODO Configuration -----
(setq org-todo-keywords
'((sequence "TODO(t)" "WAIT(w)" "|" "DONE(d!)")))
(setq org-todo-keyword-faces
'(("GOAL" . (:foreground "orange red" :weight bold))
("WAIT" . (:foreground "HotPink2" :weight bold))
("BACK" . (:foreground "MediumPurple3" :weight bold))))
;;; ----- Context Tags -----
(setq-default org-tag-alist
'((:startgroup)
("Areas")
(:grouptags)
("@home" . ?H)
("@work" . ?W)
(:endgroup)
(:startgrouptag . nil)
("Contexts")
(:grouptags)
("@computer" . ?C)
("@mobile" . ?M)
("@calls" . ?A)
("@errands" . ?E)
(:endgrouptag)
;; Task Types
(:startgrouptag . nil)
("Types")
(:grouptags)
("@easy" . ?e)
("@hacking" . ?h)
("@writing" . ?w)
("@creative" . ?v)
("@accounting" . ?a)
("@email" . ?m)
("@system" . ?s)
(:endgrouptag)
;; Workflow states
(:startgroup . nil)
("States")
(:grouptags)
("@plan" . ?p)
("@review" . ?r)
("@followup" . ?f)
(:endgroup)))
;; Only make context tags inheritable (what about noexport?)
(setq org-use-tag-inheritance "^@")
;;; ----- Time Tracking -----
;; Clock in on the current task when setting a timer
(add-hook 'org-timer-set-hook #'org-clock-in)
;; Clock out of the current task when the timer is complete
(add-hook 'org-timer-done-hook #'org-clock-out)
;;; ----- Agenda Configuration -----
(defvar dw/base-agenda-files '("Inbox.org" "Schedule.org")
"The base agenda files that will always be included.")
(setq org-agenda-span 'day
org-agenda-start-with-log-mode t
org-agenda-files dw/base-agenda-files
org-agenda-window-setup 'current-window)
;; Make done tasks show up in the agenda log
(setq org-log-done 'time
org-log-into-drawer t)
;;; ----- Denote Integration -----
(defun dw/refresh-agenda-files ()
(interactive)
(setq org-agenda-files
(append (denote-directory-files "_pra")
dw/base-agenda-files)))
(defun dw/goto-weekly-note ()
(interactive)
(let* ((note-title (format-time-string "%Y-W%V"))
(existing-notes
(denote-directory-files (format "-%s" note-title) nil t)))
(if existing-notes
(find-file (car existing-notes))
(denote note-title '("plw")))))
(with-eval-after-load 'denote
;; Quick access commands
(keymap-set global-map "C-c n w" #'dw/goto-weekly-note)
;; Refresh agenda files the first time
(dw/refresh-agenda-files)
;; Update agenda files after notes are created or renamed
(add-hook 'denote-after-rename-file-hook #'dw/refresh-agenda-files)
(add-hook 'denote-after-new-note-hook #'dw/refresh-agenda-files))
(provide 'dw-workflow)
I’m planning to develop a new presentation configuration using Prot’s Logos package because it’s a bit more flexible and will enable me to treat code files as presentations in addition to Org files and pretty much any other type of file that has syntax that can be interpreted as a page marker (like a comment string).
;; -*- lexical-binding: t; -*-
(defun dw/present-prepare-slide ()
(when (and logos-focus-mode
(derived-mode-p 'org-mode))
(org-overview)
(org-show-entry)
(org-show-children)))
(defun dw/present-toggle ()
"Configures the buffer for a presentation."
(interactive)
(if logos-focus-mode
(progn
(setq-local face-remapping-alist nil)
(widen)
(logos-focus-mode 0))
(setq-local face-remapping-alist '((default (:height 1.5) default)
(org-document-title (:height 1.75) org-document-title)
(org-block-begin-line (:height 0.7) org-block)))
;; Narrow the buffer and start focus mode
(logos-narrow-dwim)
(logos-focus-mode 1)
;; Prepare the slide
(dw/present-prepare-slide)))
(use-package logos
:bind (([remap narrow-to-region] . logos-narrow-dwim)
([remap forward-page] . logos-forward-page-dwim)
([remap backward-page] . logos-backward-page-dwim))
:custom
(logos-outlines-are-pages t)
(logos-scroll-lock t)
:config
(setf (alist-get 'org-mode logos-outline-regexp-alist) "^\\*\\{1,2\\} +")
(add-hook 'logos-page-motion-hook #'dw/present-prepare-slide))
(provide 'dw-present)
I prefer rcirc
as my default IRC client, it’s simple and gets the job done. Right now I’m using the hosted Soju bouncer on chat.sr.ht
but hopefully I’ll move to something self-hosted soon.
;; -*- lexical-binding: t; -*-
(use-package rcirc
:ensure nil
:custom
(rcirc-default-nick "daviwil")
(rcirc-default-user-name "daviwil")
(rcirc-default-full-name "David Wilson")
(rcirc-server-alist `(("chat.sr.ht"
:port 6697
:encryption tls
:user-name "daviwil/irc.libera.chat@emacs")))
(rcirc-reconnect-delay 5)
(rcirc-fill-column 90)
(rcirc-track-ignore-server-buffer-flag t)
:config
;; Annoy me, please
(rcirc-track-minor-mode 1)
;; See: https://idiomdrottning.org/rcirc-soju
(defun-rcirc-command detach (channel)
"Detach channel to soju."
(interactive "sPart channel: ")
(let ((channel (if (> (length channel) 0) channel target)))
(rcirc-send-privmsg
process "BouncerServ"
(format
"channel update %s -detached true -reattach-on highlight" channel)))))
(provide 'dw-irc)
The 0x0.el
package is really useful and I used it for a long time. However, it’s currently only on MELPA and I really only use a couple of the functions it provides.
Here’s my own minimal implementation from scratch, please feel free to copy into your own config!
;; -*- lexical-binding: t; -*-
(defun dw/0x0-upload-text ()
(interactive)
(let* ((contents (if (use-region-p)
(buffer-substring-no-properties (region-beginning) (region-end))
(buffer-string)))
(temp-file (make-temp-file "0x0" nil ".txt" contents)))
(message "Sending %s to 0x0.st..." temp-file)
(let ((url (string-trim-right
(shell-command-to-string
(format "curl -s -F'file=@%s' https://0x0.st" temp-file)))))
(message "The URL is %s" url)
(kill-new url)
(delete-file temp-file))))
(defun dw/0x0-upload-file (file-path)
(interactive "fSelect a file to upload: ")
(message "Sending %s to 0x0.st..." file-path)
(let ((url (string-trim-right
(shell-command-to-string
(format "curl -s -F'file=@%s' https://0x0.st" (expand-file-name file-path))))))
(message "The URL is %s" url)
(kill-new url)))
(provide 'dw-0x0)
;; -*- lexical-binding: t; -*-
(use-package tracking
:demand t)
(use-package telega
:commands telega
:config
(setq telega-use-tracking-for '(or unmuted mention)
telega-completing-read-function #'completing-read
telega-msg-rainbow-title t
telega-chat-fill-column 75)
;; Show notifications in the mode line
(add-hook 'telega-load-hook #'telega-mode-line-hook)
;; Disable chat buffer auto-fill
(add-hook 'telega-chat-mode-hook #'telega-chat-auto-fill-mode)
(when (eq dw/current-distro 'void)
(setq telega-server-libs-prefix "/usr")))
(provide 'dw-telegram)
When used as part of a desktop environment, here are some packages that will make it easier for Emacs to drive most of the experience.
;; -*- lexical-binding: t; -*-
;; Integrate with the system clipboard
(unless (display-graphic-p)
(use-package xclip
:demand t
:config
(xclip-mode 1)))
(use-package bluetooth)
;; Control NetworkManager via nmcli
(use-package nm
:vc (:url "https://github.com/Kodkollektivet/emacs-nm"
:rev :newest))
(provide 'dw-desktop)
I use GPG primarily when authenticating with Git remotes or opening pass
entries. pinentry
is the tool that enables me to prompt for my GPG passphrase inside of Emacs.
auth-source-pass
enables the use of pass
entries for automatically loading credentials for certain things like my IRC bouncer password.
;; -*- lexical-binding: t; -*-
;; Use `pass` as an auth-source
(when (file-exists-p "~/.password-store")
(auth-source-pass-enable))
;; Enable GPG passphrase entry
(use-package pinentry
:demand t
:config
(pinentry-start))
(provide 'dw-auth)
This section contains settings that are specific to various systems, particularly to choose extension modules to be loaded on top of the base configuration.
(setq dw/use-config-modules
(append dw/common-config-modules
'(dw-desktop
dw-telegram)))
(setq dw/use-config-modules
(append dw/common-config-modules
'(dw-desktop)))
(setq dw/use-config-modules dw/common-config-modules)
(setq xclip-method 'powershell)