Uncharted

Merrick’s Doom Configuration

About

This is my personal Doom Emacs configuration, serving as the central nervous system for my workstation, Lagann. This setup is deeply integrated with the GNU Guix ecosystem, leveraging its package management to ensure stable, reproducible native dependencies (like liberime and pdf-tools).

The configuration is built around three core pillars:

  1. Knowledge Management: Transitioning from a fragmented setup to a centralized, Zettelkasten-inspired system using Denote. This also serves as the engine for my personal blog.
  2. Project Isolation: Utilizing tabspaces (backed by the built-in tab-bar-mode) to manage context-specific workspaces and projects without the overhead of heavy-handed workspace managers.
  3. Native Performance: Prioritizing Guix-managed packages and built-in Emacs features where possible to maintain a lean, high-performance environment.

Doom Modules

This block is copied from the doom emacs init.example.el, and then modified based on my needed modules, used to enable/disable doom modules.

;;; init.el -*- lexical-binding: t; -*-

(doom! :input
       ;;bidi              ; (tfel ot) thgir etirw uoy gnipleh
       (chinese            ; wo shuru buliao zhongwen le
        +rime              ; best input method (at least for chinese?)
        +childframe)       ; more like a input method
       ;;japanese
       ;;layout            ; auie,ctsrnm is the superior home row

       :completion
       ;;(company           ; the ultimate code completion backend
       ;; +childframe)      ; ... when your children are better than you
       (corfu +icons +orderless)  ; complete with cap(f), cape and a flying feather!
       ;;helm              ; the *other* search engine for love and life
       ;;ido               ; the other *other* search engine...
       ;; (ivy              ; a search engine for love and life
       ;;   +fuzzy          ; ... icons are nice
       ;;   +prescient)     ; ... I know what I want(ed)
       (vertico +childframe)  ; the search engine of the future

       :ui
       ;;deft              ; notational velocity for Emacs
       doom                ; what makes DOOM look the way it does
       dashboard           ; a nifty splash screen for Emacs
       doom-quit           ; DOOM quit-message prompts when you quit Emacs
       ;;(emoji +unicode)  ; 🙂
       hl-todo             ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
       ;;hydra
       indent-guides       ; highlighted indent columns
       ligatures           ; ligatures and symbols to make your code pretty again
       ;;minimap           ; show a map of the code on the side
       modeline            ; snazzy, Atom-inspired modeline, plus API
       nav-flash           ; blink the current line after jumping
       ;;neotree           ; a project drawer, like NERDTree for vim
       ophints             ; highlight the region an operation acts on
       (popup              ; tame sudden yet inevitable temporary windows
        +all               ; catch all popups that start with an asterix
        +defaults)         ; default popup rules
       ;;smooth-scroll     ; So smooth you won't believe it's not butter
       ;;tabs              ; an tab bar for Emacs
       treemacs            ; a project drawer, like neotree but cooler
       ;;unicode           ; extended unicode support for various languages
       ;;(vc-gutter +pretty)  ; vcs diff in the fringe
       vi-tilde-fringe     ; fringe tildes to mark beyond EOB
       ;;window-select     ; visually switch windows
       ;;workspaces        ; tab emulation, persistence & separate workspaces
       zen                 ; distraction-free coding or writing

       :editor
       (evil +everywhere)  ; come to the dark side, we have cookies
       file-templates      ; auto-snippets for empty files
       fold                ; (nigh) universal code folding
       (format +onsave)    ; automated prettiness
       ;;god               ; run Emacs commands without modifier keys
       ;;lispy             ; vim for lisp, for people who don't like vim
       multiple-cursors    ; editing in many places at once
       ;;objed             ; text object editing for the innocent
       ;;parinfer          ; turn lisp into python, sort of
       rotate-text         ; cycle region at point between text candidates
       snippets            ; my elves. They type so I don't have to
       (whitespace +guess +trim)  ; a butler for your whitespace
       ;;word-wrap         ; soft wrapping with language-aware indent

       :emacs
       dired               ; making dired pretty [functional]
       electric            ; smarter, keyword-based electric-indent
       ;;eww               ; the internet is gross
       ibuffer             ; interactive buffer management
       tramp               ; remote files at your arthritic fingertips
       undo                ; persistent, smarter undo for your inevitable mistakes
       vc                  ; version-control and Emacs, sitting in a tree

       :term
       eshell              ; a consistent, cross-platform shell (WIP)
       ;;shell             ; a terminal REPL for Emacs
       ;;term              ; terminals in Emacs
       ;;vterm               ; another terminals in Emacs
       ghostel             ; the best terminal emulation in Emacs

       :checkers
       (syntax +flymake)   ; tasing you for every semicolon you forget
       grammar             ; tasing grammar mistake every you make
       ;;(spell             ; tasing you for misspelling mispelling
       ;; +aspell)

       :tools
       ;; ansible
       ;;biblio            ; Writes a PhD for you (citation needed)
       ;;collab            ; buffers with friends
       ;;debugger          ; FIXME stepping through code, to help you add bugs
       direnv
       docker
       ;;editorconfig      ; let someone else argue about tabs vs spaces
       ;;ein               ; tame Jupyter notebooks with emacs
       (eval +overlay)     ; run code, run (also, repls)
       (lookup             ; helps you navigate your code and documentation
        +docsets)          ; ...or in Dash docsets locally
       (lsp +eglot +booster)  ; M-x vscode
       llm                 ; when I said you needed friends, I didn't mean...
       magit               ; a git porcelain for Emacs
       ;;+forge
       make                ; run make tasks from Emacs
       pass                ; password manager for nerds
       pdf                 ; pdf enhancements
       ;;prodigy           ; FIXME managing external services & code builders
       ;;rgb               ; creating color strings
       ;;taskrunner        ; taskrunner for all your projects
       ;;terraform         ; infrastructure as code
       ;;tmux              ; an API for interacting with tmux
       tree-sitter         ; syntax and parsing, sitting in a tree...
       ;;upload            ; map local to remote projects via ssh/ftp

       :os
       (:if IS-MAC macos)  ; improve compatibility with macOS
       ;;tty               ; improve the terminal Emacs experience

       :lang
       ;;ada               ; In strong typing we (blindly) trust
       ;;agda              ; types of types of types of types...
       ;;assembly          ; assembly for fun or debugging
       (beancount +lsp)    ; mind the GAAP
       (cc +lsp +tree-sitter)  ; C/C++/Obj-C madness
       clojure             ; java with a lisp
       common-lisp         ; if you've seen one lisp, you've seen them all
       ;;coq               ; proofs-as-programs
       ;;crystal           ; ruby at the speed of c
       ;;csharp            ; unity, .NET, and mono shenanigans
       data                ; config/data formats
       ;;(dart +flutter)   ; paint ui and not much else
       ;;dhall
       (elixir +lsp +tree-sitter)  ; erlang done right
       ;;elm               ; care for a cup of TEA?
       emacs-lisp          ; drown in parentheses
       ;;(erlang +lsp)     ; an elegant language for a more civilized age
       ;;ess               ; emacs speaks statistics
       ;;factor
       ;;faust             ; dsp, but you get to keep your soul
       ;;fortran           ; in FORTRAN, GOD is REAL (unless declared INTEGER)
       ;;fsharp            ; ML stands for Microsoft's Language
       ;;fstar             ; (dependent) types and (monadic) effects and Z3
       (gdscript +lsp)     ; the language you waited for
       (go +lsp +tree-sitter)  ; the hipster dialect
       ;;(graphql +lsp)    ; Give queries a REST
       ;;(haskell +lsp)    ; a language that's lazier than I am
       ;;hy                ; readability of scheme w/ speed of python
       ;;idris             ; a language you can depend on
       ;;json              ; At least it ain't XML
       ;;janet             ; Fun fact: Janet is me!
       ;;(java +lsp)       ; the poster child for carpal tunnel syndrome
       (javascript +lsp +tree-sitter)  ; all(hope(abandon(ye(who(enter(here))))))
       ;;julia             ; a better, faster MATLAB
       ;;(kotlin +lsp)     ; a better, slicker Java(Script)
       ;;latex             ; writing papers in Emacs has never been so fun
       ;;lean
       ;;ledger            ; be audit you can be
       (lua +fennel)       ; one-based indices? one-based indices
       markdown            ; writing docs for people to ignore
       ;;nim               ; python + lisp at the speed of c
       nix                 ; I hereby declare "nix geht mehr!"
       ;;(ocaml +lsp)      ; an objective camel
       ;;odin              ; C, minus its footguns
       (org                ; organize your plain life in plain text
        +dragndrop         ; drag & drop files/images into org buffers
        ;;+hugo            ; use Emacs for hugo blogging
        +roam2
        ;;+journal
        +pretty
        ;;+noter
        ;;+jupyter         ; ipython/jupyter support for babel
        ;;+pandoc          ; export-with-pandoc support
        +pomodoro          ; be fruitful with the tomato technique
        +present)          ; using org-mode for presentations
       ;;php               ; perl's insecure younger brother
       plantuml            ; diagrams for confusing people more
       ;;graphviz          ; diagrams for confusing yourself even more
       ;;purescript        ; javascript, but functional
       (python             ; beautiful is better than ugly
        +lsp
        +tree-sitter
        +poetry
        +pyright)
       ;;qt                ; the 'cutest' gui framework ever
       ;;(racket +lsp)     ; a DSL for DSLs
       ;;raku              ; the artist formerly known as perl6
       (rest +jq)          ; Emacs as a REST client
       ;;rst               ; ReST in peace
       (ruby +rails)       ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
       (rust +lsp +tree-sitter)  ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
       ;;scad              ; trust the preview, regret the render
       ;;scala             ; java, but good
       (scheme +guile +mit)  ; a fully conniving family of lisps
       ;;+guile)           ; guix!
       (sh +fish +lsp)     ; she sells {ba,z,fi}sh shells on the C xor
       ;;sml
       ;;solidity          ; do you need a blockchain? No.
       ;;swift             ; who asked for emoji variables?
       ;;terra             ; Earth and Moon in alignment for performance.
       (web                ; the tubes
        +html +css +lsp)
       yaml
       (zig +lsp)          ; C, but simpler

       ;; tailwindcss
       ;; mermaid

       :email
       ;;mu4e
       notmuch
       ;;(wanderlust +gmail)

       :app
       ;;calendar          ; A dated approach to timetabling
       ;;emms              ; Multimedia in Emacs is music to my ears
       everywhere          ; *leave* Emacs!? You must be joking.
       irc                 ; how neckbeards socialize
       (rss +org +youtube)  ; emacs as an RSS reader
       ;;twitter           ; twitter client https://twitter.com/vnought
       ;;transmission

       :config
       literate            ; use home made literate solution
       (default +bindings +smartparens))

Preamble

Make this file run (slightly) faster with lexical binding (see this blog post for more info).

;;; config.el -*- lexical-binding: t; -*-

Personal Info

It’s getting personal here.

(setq user-full-name "Merrick Luo"
      user-mail-address "merrick@luois.me")

Custom Packages

Some package customization become quite extensive, so I place them in separate files and add the lisp directory to the load path.

(add-to-list 'load-path (expand-file-name "lisp" doom-private-dir))

Appearance

Frame Opacity

I like the frame to be a little bit transparent, so I can see my carefully selected wallpapers a little bit.

I had to disable this for now due to the poor performance under wayland + pgtk.

(set-frame-parameter (selected-frame) 'alpha '(95 . 90))
(add-to-list 'default-frame-alist '(alpha . (95 . 90)))

Font

As coming to fonts, I’m not a affectionate person, I switch them around from time to time, and always looking for new ones too, currently I’m happy with Aporetic Serif Mono.

(setq doom-font "Aporetic Sans Mono-13"
      doom-variable-pitch-font "Aporetic Sans")
      ;;doom-serif-font "IBM Plex Serif"
      ;;doom-unicode-font "LXGW Wenkai")

org-modern relies on some symbols JetBrains Mono doesn’t have, use Iosevka as recommended.

(after! org-modern
  (setq org-modern-table nil) ;; conflict with valign

  (set-face-attribute 'org-modern-symbol nil :family "Iosevka"))

The line-spacing Hack

Line spacing makes text more readable to me, but some buffer doesn’t really work too well with it.

(setq-default line-spacing 4)

(defun m/unset-line-spacing()
  (setq-local line-spacing nil))

(add-hook! '(+doom-dashboard-mode-hook ghostel-mode-hook) #'m/unset-line-spacing)

This is now disabled as well, it doesn’t work as I expected, so i currently just disable line spacing for the terminal.

(after! vterm
  (define-advice window-body-height (:around (orig-fn &optional win pixelwise) +vterm--window-body-height-a)
    "Include line-spacing in the height calculation of the vterm buffer."
    (if (not (provided-mode-derived-p major-mode 'vterm-mode))
        (funcall orig-fn win pixelwise)
      (let ((pixel-height (funcall orig-fn win t))
            (char-height (funcall orig-fn win))
            (line-height (frame-char-height (window-frame win)))
            (line-spacing-1 (or (and line-spacing (* line-spacing 2)) 0)))
        (if pixelwise
            (- pixel-height (* char-height line-spacing-1))
          (round (/ pixel-height (+ line-height line-spacing-1))))))))

Theme

I rely on modus-themes (specifically modus-operandi) for its industry-leading accessibility and high-contrast light design. Since dark mode causes eye strain during long sessions, these themes are essential for my daily comfort and productivity.

(package! modus-themes)
(setq doom-theme 'modus-operandi)

(use-package! modus-themes
  :demand t
  :init
  (setq modus-themes-italic-constructs t
        modus-themes-bold-constructs t
        modus-themes-variable-pitch-ui nil
        modus-themes-org-blocks 'gray-background)

  ;; I customize the mode-line background to make it slightly more distinct
  ;; while staying within the modus-operandi aesthetic.
  (custom-set-variables
   '(modus-operandi-palette-overrides
     '((bg-mode-line-active "#d0d6ff"))))

  (setq modus-themes-headings
          (quote ((1 . (variable-pitch 1.05))
                  (agenda-date . (1.1))
                  (agenda-structure . (variable-pitch light 1.5))
                  (t . (variable-pitch 1)))))
  :config
  (modus-themes-load-theme 'modus-operandi))

Misc

I use evil with relative line numbers, but not really utilizing it, cuz I just press j so hard :).

(setq display-line-numbers-type 'relative)

use column style for highlight-indent-guides.

(after! highlight-indent-guides
  (setq highlight-indent-guides-method 'column))

Modeline

Some minor customization for doom-modeline.

(after! doom-modeline
  (setq doom-modeline-unicode-fallback nil
        doom-modeline-height nil
        doom-modeline-persp-name t
        doom-modeline-persp-icon nil
        doom-modeline-hud t))

Spacious Padding

Having some padding on frame looks pretty nice, especially when you’re using emacs as terminal.

(package! spacious-padding)
(use-package! spacious-padding
  :custom (spacious-padding-widths '(:internal-border-width 16
                                     :header-line-width 4
                                     :mode-line-width 1
                                     :tab-width 4
                                     :right-divider-width 16
                                     :scroll-bar-width 8))
  :config
  (spacious-padding-mode))

Better Defaults

Various global settings I collected from the internet.

(after! emacs
  (setq-default delete-by-moving-to-trash t
                window-combination-resize t
                x-stretch-cursor t)

  (setq undo-limit 80000000
        evil-want-fine-undo t
        auto-save-default t
        truncate-string-ellipsis "…"
        password-cache-expiry nil
        scroll-margin 2
        pixel-scroll-precision-mode t
        elisp-fontify-semantically t)

  (global-subword-mode 1)
  (xref-mouse-mode 1)

  (map! :leader
        (:prefix "w"
         :desc "Flip layout" "f" #'flip-window-layout
         :desc "Rotate layout" "R" #'rotate-window-layout)))

Completion

After using line-spacing, 17 items in completion is too much, reduce it to 12.

(after! vertico
  (setq vertico-count 12))

(after! corfu
  (setq +corfu-want-minibuffer-completion nil)
  (setq corfu-preselect 'first))

EVIL

Please don’t quit, EVIL.

;; :q should kill the current buffer rather than quitting emacs entirely
(evil-ex-define-cmd "q" 'kill-this-buffer)
(evil-ex-define-cmd "wq" 'doom/save-and-kill-buffer)

Spell Checking

Jinx is a better/faster spell checker, plus spell-fu doesn’t work for me anyway, prefer built-in package here because of package with native dependencies works better when installed via Guix.

(package! jinx :built-in 'prefer)
(use-package! jinx
  :hook (emacs-startup . global-jinx-mode)
  :config
  (add-to-list 'jinx-exclude-regexps '(t "\\cc")))
(map! :n "z =" #'jinx-correct)

Mac OS

Not my favorite OS, but I have to use it for work.

(when IS-MAC
  (add-to-list 'load-path "/opt/local/share/emacs/site-lisp/mu4e")
  (setq mac-command-modifier 'meta
        mac-option-modifier 'super)
  (global-unset-key [swipe-left])
  (global-unset-key [swipe-right]))

Org Mode

While I’ve migrated most of my knowledge management to Denote, Org Mode remains the foundation for my tasks, agenda, and complex document authoring.

(after! org
  (setq org-directory "~/syncthing/docs"))

Better defaults

These settings refine the Org experience, focusing on visual clarity (like hiding emphasis markers) and a clean, minimalist interface.

(after! org
  (setq org-ellipsis " …"
        org-auto-align-tags nil
        org-tags-column 0
        org-hide-emphasis-markers t))

Appearance

(after! org
  (add-hook! 'org-mode-hook #'mixed-pitch-mode)
  (setq org-startup-indented nil)
  (remove-hook! 'text-mode-hook #'display-line-numbers-mode))

Web Clip

(package! org-web-tools)
(use-package! org-web-tools :defer t)

Super Agenda

I’m using org-super-agenda to make my agenda a little bit more beautiful.

(package! org-super-agenda)
(use-package! org-super-agenda
  :after org-agenda
  :config
  (org-super-agenda-mode)
  (setq org-super-agenda-groups
        '((:name "In Progress" :todo "STRT")
          (:name "Today" :time-grid t :scheduled today :todo "STRT")
          (:name "Important" :priority "A")
          (:name "Overdue" :deadline past)
          (:name "Due Soon" :deadline future))))

Logging

(after! org
  (setq org-log-into-drawer t)

  ;; todo faces for reading list
  (add-to-list 'org-todo-keyword-faces
               '(("PAUS" . +org-todo-onhold))))

Screenshot

org-download is included in doom with +dragndrop flag, I made some customization to it.

(after! org-download
  (setq org-download-heading-lvl nil
        org-download-method 'directory
        org-download-image-org-width 800))

Verb

Verb is my API testing tool of choice, it’s like postman, but in emacs.

(package! verb)
(use-package! verb
  :after org
  :config
  (map! :localleader :map org-mode-map "v" verb-command-map))

Denote

(package! denote)
(package! denote-journal)
(package! denote-org)
(map! :map doom-leader-notes-map
      (:prefix ("d" . "Denote")
       :desc "Create new note" "d" #'denote
       :desc "Journal: Goto today" "t" #'denote-journal-new-or-existing-entry))

(use-package! denote
  :after org
  :init
  (setq denote-directory (expand-file-name "~/notes")))

(use-package! denote-journal
  :after denote
  :config
  (setq denote-journal-directory (expand-file-name "journal" denote-directory)
        denote-journal-title-format 'day-date-month-year)

  (defun +denote-agenda-files ()
    (append
     (seq-filter (lambda (f)
                   (let ((keywords (denote-extract-keywords-from-path f)))
                     (or (member "agenda" keywords) (member "tasks" keywords) (member "proj" keywords))))
                 (denote-directory-files))
     (list denote-journal-directory)))

  (defun m/update-agenda-files(&rest _)
    (setq org-agenda-files (+denote-agenda-files)))

  (after! org-agenda
    (m/update-agenda-files)
    (advice-add 'org-agenda :before #'m/update-agenda-files)
    (advice-add 'org-todo-list :before #'m/update-agenda-files)))

(use-package! denote-org :after denote)
  • Templates
    (after! denote
      (setq denote-templates
            '((journal . "#+category: Journal\n\n"))))
    
  • Agenda integration
    (after! denote-journal
      (add-to-list 'org-agenda-files (denote-journal-directory)))
    

Blogging

My blogging workflow is built on a “static-site-from-org” philosophy. All posts are managed as Denote files, allowing for easy linking and metadata management. The site is generated with ox-publish, driven by my own uncharted package which consolidates the publish project, Denote link export, OpenGraph metadata, and RSS feed into a single, testable place.

Previously this section was a sprawl of loose defvar’s, helper defun’s, and advice-add calls. That logic now lives in uncharted (a local package under ~/projs/elisp/uncharted), leaving this file with just the wiring.

RSS backend

The pipeline relies on org-publish-rss for feed generation. I maintain a custom fork to ensure proper handling of Denote links during export.

(package! org-publish-rss
  :recipe (:local-repo "~/projs/elisp/org-publish-rss"))

Uncharted

uncharted wraps the whole authoring-to-publishing flow. uncharted-setup installs the org-publish project (blog posts, static assets, and this literate config as the emacs page), the blog-aware denote: link exporter, and the OpenGraph <meta> builder.

(package! uncharted
  :recipe (:local-repo "~/projs/elisp/uncharted"))
(use-package! uncharted
  :after denote
  :init
  (map! :map doom-leader-notes-map :desc "New Blog Post" "b" #'uncharted-new-post)
  ;; Personal site identity + paths (the package ships only generic defaults).
  (setq uncharted-blog-directory       (expand-file-name "blog" denote-directory)
        uncharted-publishing-directory (expand-file-name "~/projs/mlog/public")
        uncharted-site-name            "Uncharted"
        uncharted-site-url             "https://merrick.luois.me"
        uncharted-site-description     "Uncharted"
        uncharted-post-setupfile       (expand-file-name "blog/20250420T134203__template.org" denote-directory)
        ;; Under Doom the literate config lives in `doom-user-dir', not
        ;; `user-emacs-directory', so publish the `emacs' page from there.
        uncharted-emacs-config-directory doom-user-dir)
  :config
  (uncharted-setup))

Email

I use notmuch for email management. It treats email as a searchable database, which fits my search-first workflow better than traditional folder-based clients. I use mbsync for the actual synchronization, keeping the storage and indexing layers decoupled.

(after! notmuch
  (set-popup-rule! "^\\*notmuch" :ignore t)

  (map! :map notmuch-show-mode-map
        :n "gv" #'goto-address-at-point)

  (setq notmuch-saved-searches
        '((:name "inbox" :query "tag:inbox not tag:trash" :key "i")
          (:name "todo" :query "tag:todo" :key "t")
          (:name "flagged" :query "tag:flagged" :key "f")
          (:name "sent" :query "tag:sent" :key "s")
          (:name "drafts" :query "tag:draft" :key "d")
          (:name "bank" :query "tag:bank" :key "b")
          (:name "emacs" :query "tag:emacs-devel" :key "e")))

  (define-advice notmuch-show-toggle-message (:around (orig-fn &rest args) visit-link-maybe)
    "Visit links on point if there are any, otherwise run `notmuch-show-toggle-message'."
    (if-let ((url (thing-at-point 'url 'no-properties)))
        (goto-address-at-point)
      (apply orig-fn args)))

  (setq +notmuch-mail-folder "~/.mails"
        +notmuch-sync-backend 'mbsync)
  (setq message-auto-save-directory "~/.mails/personal/Drafts")

  (setq smtpmail-smtp-server "smtp.zoho.com"
        smtpmail-smtp-user "merrick@luois.me"
        smtpmail-stream-type 'starttls
        smtpmail-smtp-service 587
        smtpmail-debug-info t
        smtpmail-debug-verb t
        message-send-mail-function 'message-smtpmail-send-it))

RSS Reader

I use elfeed for information gathering. By managing feeds via elfeed-org, I can treat my reading list as structured data, allowing for better categorization and integration with the rest of my Org-based workflow.

(setq +rss-enable-sliced-images nil)

(after! elfeed
  (setq elfeed-search-filter "@2-month-ago +unread -games -news -video -ai -short")
  ;; prevent nav-flash trigger in elfeed db buffer
  (add-to-list '+nav-flash-exclude-modes 'fundamental-mode))

Set the elfeed org file.

(after! elfeed-org
  (setq rmh-elfeed-org-files
        (list (expand-file-name "20240120T111420--rss-feeds__news_read.org" denote-directory))))

Valign

I use valign to ensure correct alignment of CJK characters in tables. This is particularly important for my reading lists and agenda views where mixed-width characters often break standard monospaced alignment.

(package! valign)
(use-package! valign
  :after elfeed
  :config
  (defun elfeed-search-print-valigned-entry (entry)
    "Print valign-ed ENTRY to the buffer."
    (let* ((date (elfeed-search-format-date (elfeed-entry-date entry)))
           (date-width (car (cdr elfeed-search-date-format)))
           (title (or (elfeed-meta entry :title) (elfeed-entry-title entry) ""))
           (title-faces (elfeed-search--faces (elfeed-entry-tags entry)))
           (feed (elfeed-entry-feed entry))
           (feed-title
            (when feed
              (or (elfeed-meta feed :title) (elfeed-feed-title feed))))
           (tags (mapcar #'symbol-name (elfeed-entry-tags entry)))
           (tags-str (mapconcat
                      (lambda (s) (propertize s 'face 'elfeed-search-tag-face))
                      tags ","))
           (title-width (- (window-width) 10 elfeed-search-trailing-width))
           (title-column (elfeed-format-column
                          title (elfeed-clamp
                                 elfeed-search-title-min-width
                                 title-width
                                 elfeed-search-title-max-width)
                          :left))
           (align-to (* (+ date-width 2 (min title-width elfeed-search-title-max-width))
                        (default-font-width))))
      (insert (propertize date 'face 'elfeed-search-date-face) " ")
      (insert (propertize title-column 'face title-faces 'kbd-help title) " ")
      (valign--put-overlay (1- (point)) (point) 'display (valign--space align-to))
      (when feed-title
        (insert (propertize feed-title 'face 'elfeed-search-feed-face) " "))
      (when tags
        (insert "(" tags-str ")"))))
  (setq elfeed-search-print-entry-function #'elfeed-search-print-valigned-entry))

Goodies

(package! elfeed-goodies :disable t)

Youtube

(package! elfeed-tube-mpv)
(use-package! elfeed-tube
  :after elfeed
  :custom ((elfeed-tube-auto-fetch-p t)
           (elfeed-tube-backend 'yt-dlp)
           (elfeed-tube-auto-save-p t))
  :config
  (elfeed-tube-setup)
  (defun m/elfeed-tube-download (entries)
    (interactive
     (list
      (cond
       ((eq major-mode 'elfeed-search-mode)
        (or (elfeed-search-selected) (ensure-list (elfeed-search-selected))))
       ((eq major-mode 'elfeed-show-mode)
        (ensure-list elfeed-show-entry))
       (t (user-error "elfeed-tube-download only works in Elfeed.")))))
    (when entries
      (if-let* (((seq-every-p #'elfeed-tube--youtube-p entries))
                (default-directory "~/videos/yt/"))
          (seq-doseq (entry entries)
            (let* ((title (elfeed-entry-title entry))
                   (link (elfeed-entry-link entry))
                   (output-file (expand-file-name (format "%s.mp4" title) default-directory)))
              (unless (file-exists-p output-file)
                (let ((proc (start-process
                             (format "yt-dlp download: %s" title)
                             (get-buffer-create (format "*elfeed-tube-yt-dlp*: %s" title))
                             "yt-dlp" "-w" "-c" "-o" "%(title)s.%(ext)s" "-f"
                             "bestvideo+bestaudio/best/best" "--add-metadata" link)))
                  (set-process-sentinel
                   proc
                   (lambda (process _)
                     (unless (process-live-p process)
                       (if (eq (process-exit-status process) 0)
                           (progn
                             (message "Finished download: %s" title)
                             (kill-buffer (process-buffer process))
                             (elfeed-untag entry 'unread))
                         (message "Download: [%s] failed" title))))))
                (message "Started download: %s" title))))
        (message "Not youtube url(s), cancelling download."))))
  :bind (:map elfeed-show-mode-map
         ("F" . elfeed-tube-fetch)
         ([remap save-buffer] . elfeed-tube-save)
         :map elfeed-search-mode-map
         ("F" . elfeed-tube-fetch)))

(use-package! elfeed-tube-mpv
  :after elfeed-tube
  :bind (:map elfeed-show-mode-map
        ("C-c C-f" . elfeed-tube-mpv-follow-mode)
        ("C-c C-w" . elfeed-tube-mpv-where))
  :custom (elfeed-tube-mpv-options '("--ytdl-format=bestvideo[height<=?1080]+bestaudio/best"
                                     "--cache=yes"
                                     "--window-scale=0.5"
                                     "--force-window=yes")))

LLM

integrating llm tools into emacs.

gptel

(after! gptel
  (setq gptel-backend (gptel-make-gh-copilot "Copilot")
        ;; gptel-model 'claude-3.7-sonnet
        gptel-model 'claude-sonnet-4))

agent-shell

(package! shell-maker)
(package! acp)
(package! agent-shell)
(use-package agent-shell)

Terminal

I’ve consolidated my terminal workflow around ghostel. It provides the best performance and terminal compatibility within Emacs, and its integration with EVIL makes it feel like a natural extension of the editor rather than a separate environment.

async shell command

(after! emacs
  (map! :map doom-leader-map
        :desc "Async shell command" "&" 'async-shell-command
        :desc "Shell command" "!" 'shell-command))

add bash completion to shell command.

(use-package! bash-completion
  :config
  (add-hook! 'shell-dynamic-complete-command 'bash-completion-dynamic-complete))

vterm

(use-package! vterm
  :defer t
  :init
  ;; Automatically compile the vterm module if needed,
  ;; though I usually rely on Guix for this.
  (setq vterm-always-compile-module t)
  :config
  (setq vterm-shell (executable-find "fish"))

  (add-to-list 'vterm-eval-cmds '("magit" magit-status))
  (add-to-list 'vterm-eval-cmds '("man" man))
  (add-to-list 'vterm-eval-cmds '("sudo-find-file" doom/sudo-find-file))

  (map! :mode vterm-mode :i "C-p" #'vterm-send-C-p)
  (map! :mode vterm-mode :i "C-u" #'vterm-send-C-u)
  (map! :mode vterm-mode :i "C-n" #'vterm-send-C-n))

ghostel

ghostel is a native terminal emulator powered by libghostty-vt — a Zig dynamic module handles VT parsing, rendering, and PTY I/O. It replaces vterm with better performance and without the maintenance burden of a compiled C module.

(use-package! ghostel
  :defer t
  :config
  (setq ghostel-shell (executable-find "fish"))

  (add-to-list 'ghostel-eval-cmds '("magit" magit-status))
  (add-to-list 'ghostel-eval-cmds '("man" man))
  (add-to-list 'ghostel-eval-cmds '("sudo-find-file" doom/sudo-find-file)))

term-mux

term-mux is my naive attempt to make a tmux like experience in emacs, it’s still in early stage, but it’s working pretty well for me.

(package! term-mux :type 'local :recipe (:local-repo "~/projs/elisp/term-mux"))
(use-package! term-mux
  :commands term-mux-toggle
  :config
  (add-hook 'term-mux-mode-hook (lambda () (mode-line-invisible-mode 1)))

  (evil-define-minor-mode-key :n term-mux-mode-map (kbd "`") #'term-mux-command-map)
  (evil-define-minor-mode-key :ni term-mux-mode-map (kbd "M-<escape>") #'term-mux-command-map)

  ;; (setq term-mux-default-terminal-setup-fn #'term-mux--setup-ghostel)
  ;; (setq term-mux-default-terminal-setup-fn #'term-mux--setup-eshell)
  (set-popup-rule! "\\*term-mux" :modeline t :size 0.4 :side 'bottom :ttl nil :quit nil))

global terminal

Dependency for term-mux-frame

(package! term-mux-frame :type 'local :recipe (:local-repo "~/projs/elisp/term-mux"))

Wrapper around term-mux-frame for some special handling in doom.

(use-package! term-mux-frame
  :config
  (defun terminal-frame ()
    "Start a new terminal frame, called from emacsclient."
    (unless (getenv "NIRI_SOCKET")
      (when-let* ((wayland (getenv "WAYLAND_DISPLAY"))
                  (runtime (or (getenv "XDG_RUNTIME_DIR")
                               (format "/run/user/%d" (user-uid))))
                  (sockets (file-expand-wildcards
                            (format "%s/niri.%s.*.sock" runtime wayland))))
        (setenv "NIRI_SOCKET" (car sockets))))
    (doom/switch-to-scratch-buffer)
    (term-mux-frame)))

Chinese

Rimel

(package! pyim :disable t)
(package! pyim-liberime :disable t)
(package! rimel :recipe (:host github :repo "emacs-rime/rimel"))
(use-package! rimel
  :config
  (setq default-input-method "rimel"
        rimel-posframe-style 'horizontal)
  (setq-default rimel-disable-predicates
                '(rimel-predicate-evil-mode-p
                  ;; rimel-predicate-prog-in-code-p
                  rimel-predicate-org-in-src-block-p))
                  ;; rimel-predicate-after-ascii-char-p
                  ;; rimel-predicate-after-alphabet-char-p
  (map! :after org :mode org-mode "C-," #'rimel-convert-string-at-point)
  (map! "C-," #'rimel-convert-string-at-point)
)

pyim

(after! pyim
  (setq pyim-indicator-modeline-string '("ㄓ" "En"))
  (setq pyim-dcache-auto-update nil)
  (setq pyim-default-scheme 'rime)

  (setq-default pyim-english-input-switch-functions
                '(pyim-probe-dynamic-english
                  pyim-probe-isearch-mode
                  pyim-probe-program-mode
                  pyim-probe-evil-normal-mode
                  pyim-probe-org-structure-template))

  (setq-default pyim-punctuation-half-width-functions
                '(pyim-probe-punctuation-line-beginning
                  pyim-probe-punctuation-after-punctuation))
  (map! :after org :mode org-mode "C-," #'pyim-convert-string-at-point)
  (map! "C-," #'pyim-convert-string-at-point))

Use a slightly narrowing unicode for pangu-spacing.

(package! pangu-spacing :disable t)
(after! pangu-spacing
  ;; (unsetq-hook! 'org-mode-hook pangu-spacing-real-insert-separtor t)
  (setq pangu-spacing-separator "\u2009"))

Translation

(package! fanyi)
(use-package! fanyi
  :commands (fanyi-dwim fanyi-dwim2))

Programming

This section contains my specific refinements for development. While Doom’s modules provide excellent defaults, I add custom configurations here to align with my preference for Tree-Sitter based syntax highlighting and specialized LSP integrations (especially for Guile and Elixir).

Common

Common packages work with all languages.

Tree-Sitter

I prefer level 3 font-lock as it provides a good balance between semantic highlighting and visual clarity without being overly distracting.

(after! treesit
  (setq treesit-font-lock-level 3
        treesit-auto-install-grammar 'ask
        treesit-enabled-modes t))

Flymake

Fancy diagnostics for Emacs 31.

(after! flymake
  (setq flymake-show-diagnostics-at-end-of-line 'fancy)
  (map! :n "[ e" #'flymake-goto-prev-error
        :n "] e" #'flymake-goto-next-error))
(package! breadcrumb :disable t)
(use-package! breadcrumb :config (breadcrumb-mode))

DevDocs

I use devdocs for offline-capable documentation. It’s integrated into my lookup workflow, allowing for quick reference without leaving the editor.

(package! devdocs)
(use-package! devdocs
  :commands (devdocs-lookup devdocs-install)
  :init
  (setq devdocs-data-dir (expand-file-name "devdocs" doom-cache-dir))
  (put 'devdocs-current-docs 'safe-local-variable 'listp)
  :config
  (set-popup-rule! "^\\*devdocs*\\*" :side 'right :size 0.40 :select t :ttl nil))

Dape

(package! dape)
(use-package! dape
  :commands (dape))

(after! dape
  (map! :map doom-leader-open-map :desc "Debugger" "d" dape-global-map))
(after! ispell
  (setq ispell-dictionary "english")
  (ispell-kill-ispell t))

Compilation

(after! compile
  (map! :map doom-leader-code-map "k" 'kill-compilation))

LSP/Eglot

I want the help buffers bigger.

(after! eglot
 (set-popup-rule! "^\\*eglot-help" :side 'right :size 0.40 :select t :ttl nil))

Setup eldoc-box to help show eglot hover info.

(package! eldoc-box)
(use-package! eldoc-box
  :after eglot
  :config
  (add-hook 'eglot-managed-mode-hook
            (lambda ()
              (remove-hook 'eldoc-documentation-functions
                           #'eglot-code-action-suggestion t)))
  (add-hook 'eglot-managed-mode-hook #'eldoc-box-hover-at-point-mode t))

Caser

Change between camelCase, snakecase, and kebab-case (dash-case).

(package! caser)
(use-package! caser
  :commands (caser-camelcase-dwim caser-snakecase-dwim caser-dashcase-dwim))

C/C++

(after! lsp
  (setq lsp-clients-clangd-executable "/home/merrick/.guix-home/profile/bin/clangd"))

(map! :map c-ts-mode-map
        :localleader
        "t" #'ff-find-other-file
        "T" #'ff-find-other-file-other-window)

TypeScript

Use the builtin treesit support for typescript, and tsx.

(after! treesit
  (setq treesit-language-source-alist
        '((typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src" nil nil)
          (tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src" nil nil)
          (kdl "https://github.com/tree-sitter-grammars/tree-sitter-kdl" "master" "src" nil nil))))

Jump to test file

(defun +typescript/toggle-tests ()
  (interactive)
  (let ((f (buffer-file-name)))
    (find-file
     (if (string-match-p "__tests__" f)
         (replace-regexp-in-string "__tests__/" "" (replace-regexp-in-string "\\.test" "" f))
       (replace-regexp-in-string "/\\([^/]+\\)\\." "/__tests__/\\1.test." f)))))

(map! :after typescript-ts-mode
      :map (typescript-ts-mode-map tsx-ts-mode-map)
      :localleader
      "s" #'+typescript/toggle-tests)

TailwindCSS

TailwindCSS need some lsp.

(package! lsp-tailwindcss :disable t :type 'local :recipe (:local-repo "~/projs/elisp/lsp-tailwindcss"))
(use-package! lsp-tailwindcss
  :after lsp-mode
  :init
  (setq lsp-tailwindcss-add-on-mode t
        lsp-tailwindcss-server-version "0.14.27"))

Above is with lsp-mode, and we need something new when working with eglot.

(after! eglot
  ;; Custom bridge for tailwind/typescript LSP support when using Eglot.
  (add-to-list 'eglot-server-programs
               '(((js-mode :language-id "javascript") (js-ts-mode :language-id "javascript")
                  (tsx-ts-mode :language-id "typescriptreact")
                  (typescript-ts-mode :language-id "typescript")
                  (typescript-mode :language-id "typescript"))
                 "rass" "typescript")))

Go

add GOPATH to the lsp library path. #+beginsrc emacs-lisp (after! lsp-go (setq lsp-go-library-directories (vconcat lsp-go-library-directories (vector (expand-file-name “pkg” (getenv “GOPATH”)))))) #+endsrc;

Lua

(after! lua-mode (setq lua-indent-level 2))

OpenGL

(package! glsl-mode)
(use-package! glsl-mode :mode "\\.glsl\\'")

Common Lisp

(package! sly-quicklisp)
(package! sly-asdf)
(use-package! sly-quicklisp :after sly)
(use-package! sly-asdf :after sly)

(after! sly
  (defun sly-add-local-project (&optional directory)
    "Add the current project to the quick lisp local projects list."
    (interactive "D")
    (let ((dir (or directory (doom-project-root))))
      (thread-last
        (format "(pushnew \"%s\" ql:*local-project-directories* :test #'equal)" dir)
        (sly-interactive-eval)))))

Clojure

allow jack in babashka repl with shortcut.

(after! cider
  (map! :map cider-mode-map :localleader "u" #'cider-jack-in-universal))

Guile Scheme

Geiser keep poping up dialog to ask me what scheme implementation I’m using, I’ll just make it to default to guile.

(after! geiser
  (setq-default geiser-scheme-implementation 'guile))

KDL (For Niri)

(package! kdl-ts-mode
  :recipe (:host github :repo "merrickluo/kdl-ts-mode"))
  ;;:recipe (:local-repo "~/projs/elisp/kdl-ts-mode"))
(use-package! kdl-ts-mode
  :mode (("\\.kdl\\'" . kdl-ts-mode)))

LSP Booster

(defun lsp-booster--advice-json-parse (old-fn &rest args)
  "Try to parse bytecode instead of json."
  (or
   (when (equal (following-char) ?#)
     (let ((bytecode (read (current-buffer))))
       (when (byte-code-function-p bytecode)
         (funcall bytecode))))
   (apply old-fn args)))
(advice-add (if (progn (require 'json)
                       (fboundp 'json-parse-buffer))
                'json-parse-buffer
              'json-read)
            :around
            #'lsp-booster--advice-json-parse)

(defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
  "Prepend emacs-lsp-booster command to lsp CMD."
  (let ((orig-result (funcall old-fn cmd test?)))
    (if (and (not test?)                             ;; for check lsp-server-present?
             (not (file-remote-p default-directory)) ;; see lsp-resolve-final-command, it would add extra shell wrapper
             lsp-use-plists
             (not (functionp 'json-rpc-connection))  ;; native json-rpc
             (executable-find "emacs-lsp-booster"))
        (progn
          (when-let ((command-from-exec-path (executable-find (car orig-result))))  ;; resolve command from exec-path (in case not found in $PATH)
            (setcar orig-result command-from-exec-path))
          (message "Using emacs-lsp-booster for %s!" orig-result)
          (cons "emacs-lsp-booster" orig-result))
      (progn
        (message "not using emacs-lsp-booster for: %s!" orig-result)
        orig-result))))
(advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command)

Yuck

(package! yuck-mode)
(use-package! yuck-mode
  :mode (("\\.yuck\\'" . yuck-mode)))

Zig

(after! lsp-zig
  (setq lsp-zig-zig-lib-path "~/.asdf/installs/zig/0.14.0/lib"))
(after! zig-mode
  (advice-add 'zig--run-cmd :around
              (lambda (f cmd &optional source &rest args)
                (apply f cmd source (append '("--color" "off") args)))))

SQL

Code Format

(after! apheleia
  (push '(sqlfmt . ("pg_format" filepath "-s2" "-g")) apheleia-formatters)
  (setf (alist-get 'sql-mode apheleia-mode-alist) 'sqlfmt))

Python

(after! (python-mode apheleia)
  (setf (alist-get 'python-mode apheleia-mode-alist) 'ruff)
  (setf (alist-get 'python-ts-mode apheleia-mode-alist) 'ruff))

Accounting

(after! beancount
  (remove-hook! beancount-mode #'flymake-bean-check-enable))

Project Management

I prefer a lightweight project management style that focuses on automation and workspace isolation.

Run make!

Automation is handled via make. I prefer using standard Makefiles for project tasks as they are portable and work well both inside and outside of Emacs.

(map! :map doom-leader-code-map :desc "Run make tasks" "m" #'+make/run)

Tabspaces

I use tabspaces (leveraging the built-in tab-bar-mode) as a replacement for Doom’s default workspace module. I find it much easier to customize and reason about. Each tab represents a project context, allowing for clean separation of buffers and window layouts without the complexity of traditional “workspace” managers.

(after! tab-bar
  (setq tab-bar-show nil))
(package! tabspaces :recipe (:host github :repo "mclear-tools/tabspaces"))
(use-package! tabspaces
  ;; use this next line only if you also use straight, otherwise ignore it.
  :hook (after-init . tabspaces-mode) ;; use this only if you want the minor-mode loaded at startup.
  :commands (tabspaces-switch-or-create-workspace
             tabspaces-open-or-create-project-and-workspace)
  :custom
  (tabspaces-use-filtered-buffers-as-default t)
  (tabspaces-default-tab "main")
  (tabspaces-remove-to-default t)
  (tabspaces-include-buffers '("*scratch*" "*doom:scratch*" "*Messages*"))
  (tabspaces-initialize-project-with-todo nil)
  (tabspaces-use-filtered-buffers-as-default nil)
  ;; (tabspaces-todo-file-name "project-todo.org")
  ;; sessions
  (tabspaces-session t)
  (tabspaces-session-auto-restore nil)
  (tab-bar-new-tab-choice "*scratch*"))

(map! :map doom-leader-map :desc "Tabspaces" "TAB" #'tabspaces-command-map)
(map! :map tabspaces-command-map :desc "Next Tab" "n" #'tab-next)
(map! :map tabspaces-command-map :desc "Previos Tab" "p" #'tab-previous)
(after! (consult tabspaces)
  (defvar consult--source-workspace
    (list :name     "Workspace Buffers"
          :narrow   ?w
          :history  'buffer-name-history
          :category 'buffer
          :state    #'consult--buffer-state
          :default  t
          :items    (lambda () (consult--buffer-query
                                :predicate #'tabspaces--local-buffer-p
                                :sort 'visibility
                                :as #'buffer-name)))
    "Set workspace buffer list for consult-buffer.")

(add-to-list 'consult-buffer-sources 'consult--source-workspace))

Special Workspaces

(defun m/tabspaces-move-buffer-to-space (buffer workspace)
  "Remove BUFFER from current workspace, and move it to WORKSPACE.
Create the WORKSPACE if not exists."
  (let ((tabspaces-remove-to-default nil))
    (tabspaces-remove-buffer buffer)
    (tabspaces-switch-or-create-workspace workspace)
    (switch-to-buffer buffer)))
  • Notes

    Whenever switching to notes file or open org agenda, goto the notes workspace.

    (defvar +notes-workspace-name "*notes*")
    
    (defun m/maybe-switch-to-notes-ws ()
      (require 'denote)
      (when (or (and (buffer-file-name)
                     (denote-file-has-denoted-filename-p (buffer-file-name))
                     (not (string= (tabspaces--current-tab-name) +notes-workspace-name)))
                (eq major-mode 'org-agenda-mode))
        (m/tabspaces-move-buffer-to-space (current-buffer) +notes-workspace-name)))
    
    (add-hook! doom-switch-buffer #'m/maybe-switch-to-notes-ws)
    (add-hook! find-file #'m/maybe-switch-to-notes-ws)
    
    (after! org-agenda
      (add-hook! org-agenda-mode #'m/maybe-switch-to-notes-ws))
    
  • RSS
    (defun =m/rss ()
      "Open elfeed buffer in a tabspaces workspace."
      (interactive)
      (tabspaces-switch-or-create-workspace +rss-workspace-name)
      (unless (memq (buffer-local-value 'major-mode
                                        (window-buffer
                                         (selected-window)))
                    '(elfeed-show-mode
                      elfeed-search-mode))
        (doom/switch-to-scratch-buffer)
        (elfeed)))
    
    (map! :map doom-leader-open-map "s" #'=m/rss)
    
  • Email
    (defvar +notmuch-workspace-name "*notmuch*"
      "Name of the workspace created by `=notmuch', dedicated to notmuch.")
    
    (defun =m/notmuch ()
      "Open notmuch buffer in a tabspaces workspace."
      (interactive)
      (tabspaces-switch-or-create-workspace +notmuch-workspace-name)
      (if-let* ((win (cl-find-if (lambda (it) (string-match-p "^\\*notmuch" (buffer-name (window-buffer it))))
                                       (doom-visible-windows))))
                (select-window win)
              (funcall +notmuch-home-function)))
    
    (defun +notmuch/quit ()
      "Close notmuch buffer and workspace."
      (interactive)
      (doom-kill-matching-buffers "^\\*notmuch")
    
      (when (string= (tabspaces--current-tab-name) +notmuch-workspace-name)
        (tabspaces-kill-buffers-close-workspace)))
    
    (map! :map doom-leader-open-map :desc "Email" "m" #'=m/notmuch)
    

Treemacs

(after! treemacs
  (setq treemacs-read-string-input 'from-minibuffer))

Entertainment

Emacs is also my portal for lighter activities, from catching up on Hacker News to participating in IRC discussions.

News

(package! hackernews)
(package! lobsters)
(use-package! hackernews :commands (hackernews))
(use-package! lobsters :commands (lobsters-hottest lobsters-newest))

Social Network

I use circe for IRC. I prefer its clean interface and robust connection management, especially when connecting to Libera.Chat for community discussions.

(after! circe
  (set-irc-server! "irc.libera.chat"
    `(:tls t
      :port 6697
      :nick "merrick"
      :sasl-username "merrick"
      :sasl-password (lambda (_server) (password-store-get "merrick/irc.libera.chat/merrick"))
      :channels ("#emacs" "#guix" "#guixcn"))))

MPV

(package! mpv)
(defun +mpv-play-url-at-point()
  "Play the current url in MPV."
  (interactive)
  (require 'mpv)

  (if-let (url (thing-at-point 'url t))
      (progn
        (message "Lauching mpv with %s" url)
        (mpv-play-url url))
    (message "no url found at point")))

(defun +elfeed-mpv-play ()
  "Play current elfeed in MPV."
  (interactive)
  (require 'mpv)
  (when-let ((entries (elfeed-search-selected)))
    (let ((url (elfeed-entry-link (car entries)))) ;; only care about one
      (message "Lauching mpv with %s" url)
      (mpv-play-url url))))

(map! :map elfeed-search-mode-map "p" #'+elfeed-mpv-play)

Guix

This section documents my integration with the GNU Guix package manager. I prioritize Guix-managed packages for anything with complex native dependencies (like liberime or pdf-tools) to avoid the brittleness of on-the-fly compilation within Emacs and to ensure consistent behavior across my systems.

;; (package! vterm :built-in 'prefer)
(package! liberime :built-in 'prefer)
(package! avy :built-in 'prefer)
(package! nerd-icons :built-in 'prefer)
(package! treemacs-nerd-icons :built-in 'prefer)
(package! notmuch :built-in 'prefer)
(package! pdf-tools :built-in 'prefer)
(package! guix :built-in 'prefer)
(use-package! guix :commands guix)