Skip to main content
  1. Posts/

My Literate Doom Emacs Configuration

·5272 words·25 mins·
Emacs Workflow
Joshua Blais
Author
Joshua Blais
Table of Contents

I have, for some time, been wanting to litreately (is that a word?) organize my Doom emacs configuration, as the config.el file is an utter mess of over 1,000 lines. I went back, organized, categorized, and updated things so that they will make sense when I want to fiddle with it (inevitably) in the future. So, without much chit-chat, here is my config.

Don't copy paste the whole thing, but do copy the things that might interest you for your own! I will be continually updating this post with the configuration changes I make, so consider it a living document.

Initial Setup

Included configuration

;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-

;; Place your private configuration here! Remember, you do not need to run 'doom
;; sync' after modifying this file!

;; Some functionality uses this to identify you, e.g. GPG configuration, email
;; clients, file templates and snippets.
(setq user-full-name "Josh Blais"
      user-mail-address "josh@joshblais.com")

Fonts

I really like GeistMono as a replacement for SourceCode Pro. Alegreya is a great writing font

;; Doom exposes five (optional) variables for controlling fonts in Doom. Here
;; are the three important ones:
(setq doom-font (font-spec :family "GeistMono Nerd Font" :size 15)
      doom-variable-pitch-font (font-spec :family "Alegreya" :size 18)
      doom-big-font (font-spec :family "GeistMono Nerd Font" :size 22))
;;   presentations or streaming.
;;
;; They all accept either a font-spec, font string ("Input Mono-12"), or xlfd
;; font string. You generally only need these two:
;; (setq doom-font (font-spec :family "monospace" :size 12 :weight 'semi-light)
;;       doom-variable-pitch-font (font-spec :family "sans" :size 13))

Theming

I like the nord theme that is packaged with doom, though I have done some customization in the past, which live in the themes directory. I also don't want a top bar, as I launch and close emacs with the keyboard, and it looks bad.

;; There are two ways to load a theme. Both assume the theme is installed and
;; available. You can either set `doom-theme' or manually load a theme with the
;; `load-theme' function. This is the default:
(add-to-list 'custom-theme-load-path "~/.config/doom/themes/")
(load-theme 'doom-nord t)

;; Maintain terminal transparency in Doom Emacs
(after! doom-themes
  (unless (display-graphic-p)
    (set-face-background 'default "undefined")))

;; remove top frame bar in emacs
(add-to-list 'default-frame-alist '(undecorated . t))

Modeline setup

(setq doom-modeline-icon t)
(setq doom-modeline-major-mode-icon t)
(setq doom-modeline-lsp-icon t)
(setq doom-modeline-major-mode-color-icon t)

Transparency

;; Transparency
(set-frame-parameter (selected-frame) 'alpha '(96 . 97))
(add-to-list 'default-frame-alist '(alpha . (96 . 97)))

Indenting

;; Aggresssive Indent
(require 'aggressive-indent)
(global-aggressive-indent-mode 1)

Cursor blink

;; Blink cursor
(blink-cursor-mode 1)

Line wrapping and tweaks

We set the visual line wrapping and relative line numbers (then we can just go up or down by X number of lines)

;; This determines the style of line numbers in effect. If set to `nil', line
;; numbers are disabled. For relative line numbers, set this to `relative'.
(setq display-line-numbers-type 'relative)

;; Line wrapping
(global-visual-line-mode t)

Setup trash and auto saving

;; Send files to trash instead of fully deleting
(setq delete-by-moving-to-trash t)
;; Save automatically
(setq auto-save-default t)

Optimizations

;; Performance optimizations
(setq gc-cons-threshold (* 256 1024 1024))
(setq read-process-output-max (* 4 1024 1024))
(setq comp-deferred-compilation t)
(setq comp-async-jobs-number 8)

;; Garbage collector optimization
(setq gcmh-idle-delay 5)
(setq gcmh-high-cons-threshold (* 1024 1024 1024))

;; Version control optimization
(setq vc-handled-backends '(Git))

Splash Screen

;; Setup custom splashscreen
(remove-hook '+doom-dashboard-functions #'doom-dashboard-widget-shortmenu)
(setq fancy-splash-image "~/Pictures/gnu_color.png")
(add-hook! '+doom-dashboard-functions :append
  (insert "\n" (+doom-dashboard--center +doom-dashboard--width "Welcome Home, Joshua.")))

Notes and misc

;; Here are some additional functions/macros that could help you configure Doom:
;;
;; - `load!' for loading external *.el files relative to this one
;; - `use-package!' for configuring packages
;; - `after!' for running code after a package has loaded
;; - `add-load-path!' for adding directories to the `load-path', relative to
;;   this file. Emacs searches the `load-path' when you load packages with
;;   `require' or `use-package'.
;; - `map!' for binding new keys
                                        ;(require 'org-mime)

Set Zen Browser as default

;; set specific browser to open links
;;(setq browse-url-browser-function 'browse-url-firefox)
;; set browser to zen-browser
(setq browse-url-browser-function 'browse-url-generic)
(setq browse-url-generic-program "zen-browser")  ; replace with actual executable name

Whichkey speedup

;; Speed of which-key popup
(setq which-key-idle-delay 0.2)

Completion

;; Completion mechanisms
(setq completing-read-function #'completing-read-default)
(setq read-file-name-function #'read-file-name-default)

;; Makes path completion more like find-file everywhere
(setq read-file-name-completion-ignore-case t
      read-buffer-completion-ignore-case t
      completion-ignore-case t)

;; Use the familiar C-x C-f interface for directory completion
(map! :map minibuffer-mode-map
      :when (featurep! :completion vertico)
      "C-x C-f" #'find-file)

(after! vertico
  ;; Add file preview
  (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)
  (define-key vertico-map (kbd "DEL") #'vertico-directory-delete-char)
  (define-key vertico-map (kbd "M-DEL") #'vertico-directory-delete-word))

;; Enhanced marginalia annotations
(after! marginalia
  (setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)))

;; Corrected Embark configuration
(map! :leader
      (:prefix ("k" . "embark")  ;; Using 'k' prefix instead of 'e' which conflicts with elfeed
       :desc "Embark act" "a" #'embark-act
       :desc "Embark dwim" "d" #'embark-dwim))

;; Optional: Make vertico use a more minimal display
(after! vertico
  (setq vertico-count 17
        vertico-cycle t))

;; Optional: Configure consult for better previews
(after! consult
  (setq consult-preview-key "M-.")
  (setq consult-ripgrep-args "rg --null --line-buffered --color=never --max-columns=1000 --path-separator /   --smart-case --no-heading --with-filename --line-number --search-zip"))

Org mode setup

Initial setup

;; If you use `org' and don't want your org files in the default location below,
;; change `org-directory'. It must be set before org loads!
(setq org-directory "~/org")

(use-package org
  :ensure nil
  :custom (org-modules '(org-habit)))

(after! org
  (map! :map org-mode-map
        :n "<M-left>" #'org-do-promote
        :n "<M-right>" #'org-do-demote)
  )

;; Auto-clock in when state changes to STRT
(defun my/org-clock-in-if-starting ()
  "Clock in when the task state changes to STRT"
  (when (and (string= org-state "STRT")
             (not (org-clock-is-active)))
    (org-clock-in)))

;; Auto-clock out when leaving STRT state
(defun my/org-clock-out-if-not-starting ()
  "Clock out when leaving STRT state"
  (when (and (org-clock-is-active)
             (not (string= org-state "STRT")))
    (org-clock-out)))

;; Add these functions to org-after-todo-state-change-hook
(add-hook 'org-after-todo-state-change-hook 'my/org-clock-in-if-starting)
(add-hook 'org-after-todo-state-change-hook 'my/org-clock-out-if-not-starting)

;; Show habits in agenda
(setq org-habit-show-all-today t)
(setq org-habit-graph-column 1)
(add-hook 'org-agenda-mode-hook
          (lambda ()
            (visual-line-mode -1)
            (setq truncate-lines 1)))

(after! org
  (use-package! org-fancy-priorities
    :hook
    (org-mode . org-fancy-priorities-mode)
    :config
    (setq org-fancy-priorities-list '("⚡" "⬆" "⬇" "☕"))))

;; Prevent clock from stopping when marking subtasks as done
(setq org-clock-out-when-done nil)

Org Tangle

;; Org-auto-tangle
(use-package org-auto-tangle
  :defer t
  :hook (org-mode . org-auto-tangle-mode)
  :config
  (setq org-auto-tangle-default t))

Org Agenda

;; Org Agenda
;; Set days viewed to 3, set start day to today, create seperator, and Dashboard view
(setq org-agenda-remove-tags t)
(setq org-agenda-block-separator 32)
(setq org-agenda-custom-commands
      '(("d" "Dashboard"
         (
          (tags "PRIORITY=\"A\""
                ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                 (org-agenda-overriding-header "\n HIGHEST PRIORITY")
                 (org-agenda-prefix-format "   %i %?-2 t%s")
                 )
                )
          (agenda ""
                  (
                   (org-agenda-start-day "+0d")
                   (org-agenda-span 1)
                   (org-agenda-time)
                   (org-agenda-remove-tags t)
                   (org-agenda-todo-keyword-format "")
                   (org-agenda-scheduled-leaders '("" ""))
                   (org-agenda-current-time-string "ᐊ┈┈┈┈┈┈┈┈┈ NOW")
                   (org-agenda-overriding-header "\n TODAY'S SCHEDULE")
                   (org-agenda-prefix-format "   %i %?-2 t%s")
                   )
                  )
          (tags-todo  "-STYLE=\"habit\""
                      (
                       (org-agenda-overriding-header "\n ALL TODO")
                       (org-agenda-sorting-strategy '(priority-down))
                       (org-agenda-remove-tags t)
                       (org-agenda-prefix-format "   %i %?-2 t%s")
                       )
                      )))))

;; Remove Scheduled tag
(setq org-agenda-scheduled-leaders '("" ""))
;; Remove holidays from agenda
(setq org-agenda-include-diary nil)

Org capture templates

;; Capture templates
(setq org-capture-templates
      '(("t" "Todo" entry
         (file+headline "~/org/inbox.org" "Inbox")
         "* TODO %^{Task}\n:PROPERTIES:\n:CREATED: %U\n:CAPTURED: %a\n:END:\n%?")
        ("e" "Event" entry
         (file+headline "~/org/calendar.org" "Events")
         "* %^{Event}\n%^{SCHEDULED}T\n:PROPERTIES:\n:CREATED: %U\n:CAPTURED: %a\n:CONTACT: %(org-capture-ref-link \"~/org/contacts.org\")\n:END:\n%?")
        ("d" "Deadline" entry
         (file+headline "~/org/calendar.org" "Deadlines")
         "* TODO %^{Task}\nDEADLINE: %^{Deadline}T\n:PROPERTIES:\n:CREATED: %U\n:CAPTURED: %a\n:END:\n%?")
        ("p" "Project" entry
         (file+headline "~/org/projects.org" "Projects")
         "* PROJ %^{Project name}\n:PROPERTIES:\n:CREATED: %U\n:CAPTURED: %a\n:END:\n** TODO %?")
        ("i" "Idea" entry
         (file+headline "~/org/ideas.org" "Ideas")
         "** IDEA %^{Idea}\n:PROPERTIES:\n:CREATED: %U\n:CAPTURED: %a\n:END:\n%?")
        ("c" "Contact" entry
         (file+headline "~/org/contacts.org" "Inbox")
         "* %^{Name}

:PROPERTIES:
:CREATED: %U
:CAPTURED: %a
:EMAIL: %^{Email}
:PHONE: %^{Phone}
:BIRTHDAY: %^{Birthday +1y}u
:LOCATION: %^{Address}
:LAST_CONTACTED: %U
:END:
\\ *** Communications
\\ *** Notes
%?")
        ("n" "Note" entry
         (file+headline "~/org/notes.org" "Inbox")
         "* [%<%Y-%m-%d %a>] %^{Title}\n:PROPERTIES:\n:CREATED: %U\n:CAPTURED: %a\n:END:\n%?"
         :prepend t)))

;; Helper function to select and link a contact
(defun org-capture-ref-link (file)
  "Create a link to a contact in contacts.org"
  (let* ((headlines (org-map-entries
                     (lambda ()
                       (cons (org-get-heading t t t t)
                             (org-id-get-create)))
                     t
                     (list file)))
         (contact (completing-read "Contact: "
                                   (mapcar #'car headlines)))
         (id (cdr (assoc contact headlines))))
    (format "[[id:%s][%s]]" id contact)))

;; Set archive location to done.org under current date
;; (defun my/archive-done-task ()
;;   "Archive current task to done.org under today's date"
;;   (interactive)
;;   (let* ((date-header (format-time-string "%Y-%m-%d %A"))
;;          (archive-file (expand-file-name "~/org/done.org"))
;;          (location (format "%s::* %s" archive-file date-header)))
;;     ;; Only archive if not a habit
;;     (unless (org-is-habit-p)
;;       ;; Add COMPLETED property if it doesn't exist
;;       (org-set-property "COMPLETED" (format-time-string "[%Y-%m-%d %a %H:%M]"))
;;       ;; Set archive location and archive
;;       (setq org-archive-location location)
;;       (org-archive-subtree))))

;; Automatically archive when marked DONE, except for habits
;; (add-hook 'org-after-todo-state-change-hook
;;           (lambda ()
;;             (when (and (string= org-state "DONE")
;;                        (not (org-is-habit-p)))
;;               (my/archive-done-task))))

;; Optional key binding if you ever need to archive manually
(define-key org-mode-map (kbd "C-c C-x C-a") 'my/archive-done-task)

Org Roam

;;Org-Roam
;; Org-Roam Configuration with SQLite Built-in Connector
(use-package! org-roam
  :custom
  ;; Set your org-roam directory
  (org-roam-directory "~/org/roam")

  ;; Explicitly use the built-in SQLite connector
  (org-roam-database-connector 'sqlite-builtin)

  ;; Set an absolute path for the database file
  (org-roam-db-location (expand-file-name "org-roam.db" org-roam-directory))

  :config
  ;; Make sure the directory exists
  (unless (file-exists-p org-roam-directory)
    (make-directory org-roam-directory t))

  ;; Add error handling for database operations
  (advice-add 'org-roam-db-query :around
              (lambda (fn &rest args)
                (condition-case err
                    (apply fn args)
                  (error
                   (message "Database error in org-roam: %S" err)
                   nil))))

  ;; Enable auto-sync mode to keep the database updated
  (org-roam-db-autosync-mode +1))

;; Org-Roam UI setup - only load after org-roam is properly initialized
(use-package! websocket
  :after org-roam)

(use-package! org-roam-ui
  :after org-roam
  :config
  (setq org-roam-ui-sync-theme t
        org-roam-ui-follow t
        org-roam-ui-update-on-save t
        org-roam-ui-open-on-start t))

;; org-download customizations
(require 'org-download)
(setq-default org-download-screenshot-method "scrot -s %s")

;; Debugging function for SQLite issues
(defun debug-org-roam-db ()
  "Debug function to test org-roam database connection."
  (interactive)
  (message "Testing org-roam database...")
  (message "Directory exists: %s" (file-exists-p org-roam-directory))
  (message "Database path: %s" org-roam-db-location)
  (message "Database connector: %s" org-roam-database-connector)
  (condition-case err
      (progn
        (org-roam-db-sync)
        (message "Database synced successfully!"))
    (error (message "Database sync error: %S" err))))

Org keybinds

;; Keybinds for org mode
(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-c C-i") #'my/org-insert-image)
  (define-key org-mode-map (kbd "C-c e") #'org-set-effort)
  (define-key org-mode-map (kbd "C-c i") #'org-clock-in)
  (define-key org-mode-map (kbd "C-c o") #'org-clock-out))

Custom function for image insertion

;; Insert image into org from selection
(defun my/org-insert-image ()
  "Select and insert an image into org file."
  (interactive)
  (let ((selected-file (read-file-name "Select image: " "~/Pictures/" nil t)))
    (when selected-file
      (insert (format "[[file:%s]]\n" selected-file))
      (org-display-inline-images))))

Org Babel

(after! org
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((go . t)))

  (setq org-src-fontify-natively t
        org-src-preserve-indentation t
        org-src-tab-acts-natively t
        ;; Don't save source edits in temp files
        org-src-window-setup 'current-window))

;; Specifically for go-mode literate programming
(defun org-babel-edit-prep:go (babel-info)
  (when-let ((tangled-file (->> babel-info caddr (alist-get :tangle))))
    (let ((full-path (expand-file-name tangled-file)))
      ;; Don't actually create/modify the tangled file
      (setq-local buffer-file-name full-path)
      (lsp-deferred))))

Evil mode setup

This sets up k-j as an evil escape sequence. Not used anymore due to setting esc keys in firmware.

;; Evil-escape sequence
(setq-default evil-escape-key-sequence "kj")
(setq-default evil-escape-delay 0.1)

; Don't move cursor back when exiting insert mode
(setq evil-move-cursor-back nil)
;; granular undo with evil mode
(setq evil-want-fine-undo t)
;; Enable paste from system clipboard with C-v in insert mode
(evil-define-key 'insert global-map (kbd "C-v") 'clipboard-yank)

Vterm

;; Vterm adjustemts
(setq vterm-environment '("TERM=xterm-256color"))
(set-language-environment "UTF-8")
(set-default-coding-systems 'utf-8)
(custom-set-faces!
  '(vterm :family "Geistmono Nerd Font"))

;; open vterm in dired location
(after! vterm
  (setq vterm-buffer-name-string "vterm %s")

  ;; Modify the default vterm opening behavior
  (defadvice! +vterm-use-current-directory-a (fn &rest args)
    "Make vterm open in the directory of the current buffer."
    :around #'vterm
    (let ((default-directory (or (and (buffer-file-name)
                                      (file-name-directory (buffer-file-name)))
                                 (and (eq major-mode 'dired-mode)
                                      (dired-current-directory))
                                 default-directory)))
      (apply fn args)))

  ;; Also modify Doom's specific vterm functions
  (defadvice! +vterm-use-current-directory-b (fn &rest args)
    "Make Doom's vterm commands open in the directory of the current buffer."
    :around #'+vterm/here
    (let ((default-directory (or (and (buffer-file-name)
                                      (file-name-directory (buffer-file-name)))
                                 (and (eq major-mode 'dired-mode)
                                      (dired-current-directory))
                                 default-directory)))
      (apply fn args))))

Development

Emmet

;; Emmet remap
(add-hook 'sgml-mode-hook 'emmet-mode) ;; Auto-start on any markup modes
(add-hook 'css-mode-hook  'emmet-mode) ;; enable Emmet's css abbreviation.
(map! :map emmet-mode-keymap
      :n "<C-return>" #'emmet-expand-line)
(setq emmet-expand-jsx-className? t) ;; default nil

LSP

;; LSP Performance optimizations and settings
(after! lsp-mode
  (setq lsp-idle-delay 0.5
        lsp-log-io nil
        lsp-completion-provider :capf
        lsp-enable-file-watchers nil
        lsp-enable-folding nil
        lsp-enable-text-document-color nil
        lsp-enable-on-type-formatting nil
        lsp-enable-snippet nil
        lsp-enable-symbol-highlighting nil
        lsp-enable-links nil

        ;; Go-specific settings
        lsp-go-hover-kind "Synopsis"
        lsp-go-analyses '((fieldalignment . t)
                          (nilness . t)
                          (unusedwrite . t)
                          (unusedparams . t))

        ;; Register custom gopls settings
        lsp-gopls-completeUnimported t
        lsp-gopls-staticcheck t
        lsp-gopls-analyses '((unusedparams . t)
                             (unusedwrite . t))))

;; LSP UI settings for better performance
(after! lsp-ui
  (setq lsp-ui-doc-enable t
        lsp-ui-doc-position 'at-point
        lsp-ui-doc-max-height 8
        lsp-ui-doc-max-width 72
        lsp-ui-doc-show-with-cursor t
        lsp-ui-doc-delay 0.5
        lsp-ui-sideline-enable nil
        lsp-ui-peek-enable t))

Treesitter

;; Enable Treesitter for Go in org
(after! tree-sitter
  (require 'tree-sitter-langs)
  (add-to-list 'tree-sitter-major-mode-language-alist '(org-mode . go)))

Svelte and JS

(use-package! svelte-mode
  :mode "\\.svelte\\'"
  :config
  (setq svelte-basic-offset 2)
  ;; Disable automatic reformatting
  (setq svelte-format-on-save nil)
  ;; Use prettier instead
  (add-hook 'svelte-mode-hook 'prettier-js-mode))

;; Configure prettier
(use-package! prettier-js
  :config
  (setq prettier-js-args
        '("--parser" "svelte"
          "--tab-width" "2"
          "--use-tabs" "true")))

Tailwind

;; Tailwind CSS
(use-package! lsp-tailwindcss)

Company

;; Company mode tweaks
(after! company
  (setq company-minimum-prefix-length 1
        company-idle-delay 0.1
        company-show-quick-access t
        company-tooltip-limit 20
        company-tooltip-align-annotations t)
  ;; Add file path completion
  (add-to-list 'company-backends 'company-files)
  (setq company-files-exclusions nil)
  (setq company-files-chop-trailing-slash t))

Minimap (not using)

;; ;; Setup Minimap
;; (require 'sublimity)
;; (require 'sublimity-scroll)
;; (require 'sublimity-map) ;; experimental
;; (require 'sublimity-attractive)
;; ;; Minimap settings
;; (setq minimap-window-location 'right)
;; (map! :leader
;;       (:prefix ("t" . "toggle")
;;        :desc "Toggle minimap-mode" "m" #'minimap-mode))

Treemacs

;; Treemacs
(require 'treemacs-all-the-icons)
(setq doom-themes-treemacs-theme "all-the-icons")

AI

;; (use-package! elysium
;;   :custom
;;   (elysium-window-size 0.33)
;;   (elysium-window-style 'vertical))

;; (use-package! gptel
;;   :custom
;;   (gptel-model 'claude-3-5-sonnet-20241022)
;;   :config
;;   (defun gptel-api-key ()
;;     "Read API key from file and ensure it's clean."
;;     (string-trim
;;      (with-temp-buffer
;;        (insert-file-contents "~/secrets/claude_key")
;;        (buffer-string))))

;;   (setq gptel-backend
;;         (gptel-make-anthropic "Claude"
;;                               :stream t
;;                               :key (gptel-api-key))))  ; Call the function immediately

Magit

(defun my/magit-stage-commit-push ()
  "Stage all, commit with quick message, and push with no questions"
  (interactive)
  (magit-stage-modified)
  (let ((msg (read-string "Commit message: ")))
    (magit-commit-create (list "-m" msg))
    (magit-run-git "push" "origin" (magit-get-current-branch))))

DAP

(after! dap-mode
  (require 'dap-dlv-go)

  ;; Remove problematic hooks
  (remove-hook 'dap-stopped-hook 'dap-ui-repl-toggle)
  (remove-hook 'dap-session-created-hook 'dap-ui-mode))

TRAMP

;;;; TRAMP optimizations
(after! tramp
  (setq tramp-default-method "ssh"          ; Use SSH by default
        tramp-verbose 1                      ; Reduce verbosity
        tramp-use-ssh-controlmaster-options nil  ; Don't use control master
        tramp-chunksize 500                 ; Bigger chunks for better performance
        tramp-connection-timeout 10         ; Shorter timeout
        ;; Use SSH configuration
        tramp-use-ssh-controlmaster-options nil
        ;; Cache remote files
        remote-file-name-inhibit-cache nil
        ;; Enable file-name-handler cache
        tramp-cache-read-persistent-data t))

;; Additional performance settings
(setq vc-ignore-dir-regexp
      (format "%s\\|%s"
              vc-ignore-dir-regexp
              tramp-file-name-regexp))

SQL mode

;; Setup development SQL database
(setq sql-connection-alist
      '((dev-postgres
         (sql-product 'postgres)
         (sql-server "localhost")
         (sql-user "postgres")
         (sql-password "postgres")
         (sql-database "devdb")
         (sql-port 5432))))

;; Configure org-babel SQL connection parameters
(setq org-babel-default-header-args:sql
      '((:engine . "postgresql")
        (:dbhost . "localhost")
        (:dbuser . "postgres")
        (:dbpassword . "postgres")
        (:database . "devdb")))

;; Ensure we have org-babel SQL support
(with-eval-after-load 'org
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((sql . t))))

;; PGmacs setup
(use-package pgmacs
  :after pg
  :commands (pgmacs pgmacs-open-string pgmacs-open-uri)
  :config
  ;; Define a function to quickly connect to your development database
  (defun my-pgmacs-connect ()
    "Connect to the development database using PGmacs."
    (interactive)
    (pgmacs-open-string "user=postgres password=postgres dbname=devdb host=localhost port=5432"))

  ;; Set PGmacs customization options
  (setq pgmacs-default-display-limit 100)  ;; Default number of rows to show
  (setq pgmacs-widget-use-proportional-font nil))  ;; Use fixed-width font in widgets

;; Modified function to use existing SQL connection when available
(defun pg-query-to-orgtable (query &optional buffer-name)
  "Execute PostgreSQL QUERY and insert results as an Org table."
  (interactive "sSQL Query: \nsBuffer name (default *SQL Results*): ")
  (let ((buffer (get-buffer-create (or buffer-name "*SQL Results*"))))
    ;; Check if we have an active SQL connection
    (if (and (boundp 'sql-buffer) (buffer-live-p sql-buffer))
        ;; Use the SQL buffer method if we have a connection
        (progn
          (with-current-buffer buffer
            (erase-buffer)
            (org-mode)
            (insert "#+TITLE: SQL Query Results\n")
            (insert "#+DATE: " (format-time-string "%Y-%m-%d") "\n\n")
            (insert "#+BEGIN_SRC sql\n")
            (insert query "\n")
            (insert "#+END_SRC\n\n"))

          ;; Format the SQL output for better parsing
          (sql-send-string "\\a")  ;; Unaligned mode
          (sql-send-string "\\t")  ;; Tuples only
          (sql-send-string "\\f '|'")  ;; Field separator
          (sit-for 0.3)

          ;; Execute the query
          (sql-send-string query)
          (sit-for 1.0)

          ;; Add a marker to find the end of results
          (sql-send-string "SELECT '---RESULT-END---';")
          (sit-for 0.5)

          ;; Parse results from SQL buffer
          (with-current-buffer sql-buffer
            (save-excursion
              (goto-char (point-max))
              (when (search-backward "---RESULT-END---" nil t)
                (let ((end-pos (match-beginning 0)))
                  (search-backward query nil t)
                  (forward-line 1)
                  (let ((result-text (buffer-substring-no-properties (point) end-pos)))
                    (with-current-buffer buffer
                      (goto-char (point-max))
                      (let ((lines (split-string result-text "\n" t)))
                        (dolist (line lines)
                          (unless (string-match-p "^\\(devdb\\|Output\\|Tuples\\|Field\\)" line)
                            (unless (string-equal "" (string-trim line))
                              (insert "| ")
                              (insert (mapconcat 'identity
                                                (split-string line "|")
                                                " | "))
                              (insert " |\n"))))
                        (when (search-backward "|" nil t)
                          (org-table-align)))))))))

          ;; Reset SQL formatting
          (sql-send-string "\\a")
          (sql-send-string "\\t"))

      ;; Otherwise use org-babel with explicit connection parameters
      (with-current-buffer buffer
        (erase-buffer)
        (org-mode)
        (insert "#+TITLE: SQL Query Results\n")
        (insert "#+DATE: " (format-time-string "%Y-%m-%d") "\n\n")
        (insert "#+begin_src sql :engine postgresql :dbhost localhost :dbuser postgres :dbpassword postgres :database devdb :exports both\n")
        (insert query)
        (insert "\n#+end_src\n\n")
        (goto-char (point-min))
        (search-forward "#+begin_src")
        (forward-line 1)
        (org-babel-execute-src-block)))

    (switch-to-buffer buffer)
    (goto-char (point-min))))

;; Bridge function to export PGmacs data to Org documents
(defun my-pg-export-table-to-org (table-name)
  "Export a table from database to an Org document with query results."
  (interactive "sTable name: ")
  (pg-query-to-orgtable (format "SELECT * FROM %s LIMIT 100;" table-name)))

;; All our existing functions kept for backward compatibility
(defun pg-table-to-orgtable (table-name &optional limit-rows where-clause)
  "Select data from TABLE-NAME and display as an Org table.
Optionally limit results with LIMIT-ROWS and/or filter with WHERE-CLAUSE."
  (interactive
   (list (read-string "Table name: ")
         (read-string "Limit rows (default 100): " nil nil "100")
         (read-string "WHERE clause (optional): ")))
  (let ((query (format "SELECT * FROM %s%s%s"
                      table-name
                      (if (and where-clause (not (string-empty-p where-clause)))
                          (format " WHERE %s" where-clause)
                        "")
                      (if (and limit-rows (not (string-empty-p limit-rows)))
                          (format " LIMIT %s" limit-rows)
                        ""))))
    (pg-query-to-orgtable query (format "*Table: %s*" table-name))))

(defun pg-browse-table (table-name)
  "Browse a PostgreSQL table in Org mode."
  (interactive "sTable name: ")
  (pg-table-to-orgtable table-name))

(defun pg-list-tables ()
  "List tables in the PostgreSQL database and make them clickable."
  (interactive)
  (if (and (boundp 'sql-buffer) (buffer-live-p sql-buffer))
      (let ((buf (get-buffer-create "*PG Tables*")))
        (with-current-buffer buf
          (erase-buffer)
          (org-mode)
          (insert "#+TITLE: PostgreSQL Tables\n\n")

          ;; Send command to list tables
          (sql-send-string "\\dt")
          (sit-for 0.5)

          ;; Capture the results
          (with-current-buffer sql-buffer
            (let ((tables-text (buffer-substring-no-properties
                               (save-excursion
                                 (goto-char (point-max))
                                 (forward-line -15)
                                 (point))
                               (point-max))))
              (with-current-buffer buf
                (insert "| Schema | Table | Action |\n")
                (insert "|--------+-------+--------|\n")
                ;; Parse the table list
                (let ((lines (split-string tables-text "\n" t)))
                  (dolist (line lines)
                    (when (string-match "^ *\\([^ |]*\\) *| *\\([^ |]*\\)" line)
                      (let ((schema (match-string 1 line))
                            (table (match-string 2 line)))
                        (unless (or (string= schema "Schema")
                                    (string-match-p "^--" schema)
                                    (string-match-p "^(" schema))
                          (insert (format "| %s | %s | [[elisp:(pg-browse-table \"%s\")][Browse]] | [[elisp:(my-pg-export-table-to-org \"%s\")][Export]] | [[elisp:(pgmacs-display-table \"%s\")][PGmacs]] |\n"
                                         schema table table table table))))))))))
          (org-table-align))
        (switch-to-buffer buf))
    ;; Use org-babel if no SQL connection
    (let ((buf (get-buffer-create "*PG Tables*")))
      (with-current-buffer buf
        (erase-buffer)
        (org-mode)
        (insert "#+TITLE: PostgreSQL Tables\n\n")
        (insert "#+begin_src sql :engine postgresql :dbhost localhost :dbuser postgres :dbpassword postgres :database devdb :exports both\n")
        (insert "SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema='public' ORDER BY table_name;\n")
        (insert "#+end_src\n\n")
        (goto-char (point-min))
        (search-forward "#+begin_src")
        (forward-line 1)
        (org-babel-execute-src-block)

        ;; Create links for each table - with additional options
        (when (search-forward "#+RESULTS:" nil t)
          (forward-line 1)
          (let ((start (point)))
            (forward-line)  ;; Skip header row
            (while (and (not (eobp)) (looking-at "^| "))
              (when (looking-at "| *\\([^ |]+\\) *| *\\([^ |]+\\) *|")
                (let ((schema (match-string-no-properties 1))
                      (table (match-string-no-properties 2)))
                  (delete-region (line-beginning-position) (line-end-position))
                  (insert (format "| %s | %s | [[elisp:(pg-browse-table \"%s\")][Browse]] | [[elisp:(my-pg-export-table-to-org \"%s\")][Export]] | [[elisp:(pgmacs-display-table \"%s\")][PGmacs]] |"
                                 schema table table table table))))
              (forward-line 1))
            (org-table-align))))
      (switch-to-buffer buf))))

(defun pg-describe-table (table-name)
  "Show detailed information about a table structure."
  (interactive "sTable name: ")
  (let ((buf (get-buffer-create (format "*Table Structure: %s*" table-name))))
    (with-current-buffer buf
      (erase-buffer)
      (org-mode)
      (insert (format "#+TITLE: Table Structure: %s\n\n" table-name))

      ;; Column information
      (insert "* Columns\n\n")
      (let ((query (format "SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = '%s'
ORDER BY ordinal_position;" table-name)))
        (pg-query-to-orgtable query))

      ;; Constraints
      (insert "\n* Constraints\n\n")
      (let ((query (format "SELECT c.conname AS constraint_name,
       CASE c.contype
         WHEN 'c' THEN 'check'
         WHEN 'f' THEN 'foreign_key'
         WHEN 'p' THEN 'primary_key'
         WHEN 'u' THEN 'unique'
       END AS constraint_type,
       pg_get_constraintdef(c.oid) AS constraint_definition
FROM pg_constraint c
JOIN pg_namespace n ON n.oid = c.connamespace
JOIN pg_class t ON t.oid = c.conrelid
WHERE t.relname = '%s'
  AND n.nspname = 'public';" table-name)))
        (pg-query-to-orgtable query))

      ;; Indexes
      (insert "\n* Indexes\n\n")
      (let ((query (format "SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = '%s';" table-name)))
        (pg-query-to-orgtable query)))
    (switch-to-buffer buf)))

(defun pg-sample-data (table-name)
  "Show sample data from a table with ability to filter."
  (interactive "sTable name: ")
  (let* ((where (read-string "WHERE clause (optional): "))
         (limit (read-string "Limit (default 10): " nil nil "10"))
         (query (format "SELECT * FROM %s%s LIMIT %s;"
                      table-name
                      (if (string-empty-p where) "" (format " WHERE %s" where))
                      limit)))
    (pg-query-to-orgtable query (format "*Sample: %s*" table-name))))

(defun pg-execute-buffer-query ()
  "Execute the current SQL buffer as a query and show results."
  (interactive)
  (pg-query-to-orgtable (buffer-string)))

(defun pg-execute-statement-at-point ()
  "Execute the SQL statement at point."
  (interactive)
  (let* ((bounds (bounds-of-thing-at-point 'paragraph))
         (statement (buffer-substring-no-properties (car bounds) (cdr bounds))))
    (pg-query-to-orgtable statement)))

(defun pg-connect ()
  "Connect to PostgreSQL database."
  (interactive)
  (sql-connect 'dev-postgres))

;; Key bindings for SQL mode
(with-eval-after-load 'sql
  (define-key sql-mode-map (kbd "C-c C-c") 'pg-execute-buffer-query)
  (define-key sql-mode-map (kbd "C-c C-r") 'pg-execute-statement-at-point)
  (define-key sql-mode-map (kbd "C-c t") 'pg-list-tables)
  (define-key sql-mode-map (kbd "C-c d") 'pg-describe-table))

;; Global key bindings for database operations
(map! :leader
      (:prefix-map ("e" . "custom")
       (:prefix ("d" . "database")
        :desc "Connect to PGmacs" "c" #'my-pgmacs-connect
        :desc "Open PGmacs" "p" #'pgmacs
        :desc "List tables" "t" #'pg-list-tables
        :desc "Connect to SQL" "s" #'pg-connect
        :desc "Execute SQL query" "q" #'pg-query-to-orgtable)))

Docker

(setq docker-command "podman")
(setq docker-compose-command "podman-compose")

Writing

Spelling

;; Spelling
(setq ispell-program-name "aspell")
(setq ispell-extra-args '("--sug-mode=ultra" "--lang=en_US"))
(setq spell-fu-directory "~/+STORE/dictionary") ;; Please create this directory manually.
(setq ispell-personal-dictionary "~/+STORE/dictionary/.pws")

;; Dictionary
(setq +lookup-dictionary-provider 'define-word)

;;Snippets
(yas-global-mode 1)
(add-hook 'yas-minor-mode-hook (lambda () (yas-activate-extra-mode 'fundamental-mode)))

Writeroom/Zen modes

;; Setup writeroom width and appearance
(after! writeroom-mode
  ;; Set width for centered text
  (setq writeroom-width 40)

  ;; Ensure the text is truly centered horizontally
  (setq writeroom-fringes-outside-margins nil)
  (setq writeroom-center-text t)

  ;; Add vertical spacing for better readability
  (setq writeroom-extra-line-spacing 4)  ;; Adds space between lines

  ;; Improve vertical centering with visual-fill-column integration
  (add-hook! 'writeroom-mode-hook
    (defun my-writeroom-settings ()
      "Configure various settings when entering/exiting writeroom-mode."
      (if writeroom-mode
          (progn
            ;; When entering writeroom mode
            (display-line-numbers-mode -1)       ;; Turn off line numbers
            (setq cursor-type 'bar)              ;; Change cursor to a thin bar for writing
            (hl-line-mode -1)                    ;; Disable current line highlighting
            (setq left-margin-width 0)           ;; Let writeroom handle margins
            (setq right-margin-width 0)
            (text-scale-set 1)                   ;; Slightly increase text size

            ;; Improve vertical centering
            (when (bound-and-true-p visual-fill-column-mode)
              (visual-fill-column-mode -1))      ;; Temporarily disable if active
            (setq visual-fill-column-width 40)   ;; Match writeroom width
            (setq visual-fill-column-center-text t)
            (setq visual-fill-column-extra-text-width '(0 . 0))

            ;; Set top/bottom margins to improve vertical centering
            ;; These larger margins push content toward vertical center
            (setq-local writeroom-top-margin-size
                        (max 10 (/ (- (window-height) 40) 3)))
            (setq-local writeroom-bottom-margin-size
                        (max 10 (/ (- (window-height) 40) 3)))

            ;; Enable visual-fill-column for better text placement
            (visual-fill-column-mode 1))

        ;; When exiting writeroom mode
        (progn
          (display-line-numbers-mode +1)       ;; Restore line numbers
          (setq cursor-type 'box)              ;; Restore default cursor
          (hl-line-mode +1)                    ;; Restore line highlighting
          (text-scale-set 0)                   ;; Restore normal text size
          (when (bound-and-true-p visual-fill-column-mode)
            (visual-fill-column-mode -1))))))  ;; Disable visual fill column mode

  ;; Hide modeline for a cleaner look
  (setq writeroom-mode-line nil)

  ;; Add additional global effects for writeroom
  (setq writeroom-global-effects
        '(writeroom-set-fullscreen        ;; Enables fullscreen
          writeroom-set-alpha             ;; Adjusts frame transparency
          writeroom-set-menu-bar-lines
          writeroom-set-tool-bar-lines
          writeroom-set-vertical-scroll-bars
          writeroom-set-bottom-divider-width))

  ;; Set frame transparency
  (setq writeroom-alpha 0.95))

Keybindings

Zoom

;; zoom in/out like we do everywhere else.
(global-set-key (kbd "C-=") 'text-scale-increase)
(global-set-key (kbd "C--") 'text-scale-decrease)

General

;; Custom keymaps
(map! :leader
      ;; Magit mode mappngs
      (:prefix ("g" . "magit")  ; Use 'g' as the main prefix
       :desc "Stage all files"          "a" #'magit-stage-modified
       :desc "Push"                     "P" #'magit-push
       :desc "Pull"                     "p" #'magit-pull
       :desc "Merge"                    "m" #'magit-merge
       :desc "Quick commit and push"    "z" #'my/magit-stage-commit-push
       )
      ;; Org mode mappings
      (:prefix("y" . "org-mode-specifics")
       :desc "MU4E org mode"                    "m" #'mu4e-org-mode
       :desc "Mail add attachment"              "a" #'mail-add-attachment
       :desc "Export as markdown"               "e" #'org-md-export-as-markdown
       :desc "Preview markdown file"            "p" #'markdown-preview
       :desc "Export as html"                   "h" #'org-html-export-as-html
       :desc "Org Roam UI"                      "u" #'org-roam-ui-mode
       :desc "Search dictionary at word"        "d" #'dictionary-lookup-definition
       :desc "Powerthesaurus lookup word"       "t" #'powerthesaurus-lookup-word-at-point
       :desc "Read Aloud This"                  "r" #'read-aloud-this
       :desc "Export as LaTeX then PDF"         "l" #'org-latex-export-to-pdf
       :desc "spell check"                      "z" #'ispell-word
       :desc "Find definition"                  "f" #'lsp-find-definition
       )
      ;; Mappings for Elfeed and ERC
      (:prefix("e" . "Elfeed and ERC")
       :desc "Open elfeed"              "e" #'elfeed
       :desc "Open ERC"                 "r" #'erc
       :desc "Open EWW Browser"         "w" #'eww
       :desc "Update elfeed"            "u" #'elfeed-update
       :desc "MPV watch video"          "v" #'elfeed-tube-mpv
       :desc "Open Elpher"              "l" #'elpher
       :desc "Open Pass"                "p" #'pass
       )
      ;; Various other commands
      (:prefix("o" . "open")
       :desc "Calendar"                  "c" #'=calendar
       :desc "Bookmarks"                 "l" #'list-bookmarks
       )
      (:prefix("b" . "+buffer")
       :desc "Save Bookmarks"                 "P" #'bookmark-save
       ))

;; Saving
(map! "C-s" #'save-buffer)

;; Moving between splits
(map! :map general-override-mode-map
      "C-<right>" #'evil-window-right
      "C-<left>"  #'evil-window-left
      "C-<up>"    #'evil-window-up
      "C-<down>"  #'evil-window-down
      ;; Window resizing with Shift
      "S-<right>" (lambda () (interactive)
                    (if (window-in-direction 'left)
                        (evil-window-decrease-width 5)
                      (evil-window-increase-width 5)))
      "S-<left>"  (lambda () (interactive)
                    (if (window-in-direction 'right)
                        (evil-window-decrease-width 5)
                      (evil-window-increase-width 5)))
      "S-<up>"    (lambda () (interactive)
                    (if (window-in-direction 'below)
                        (evil-window-decrease-height 2)
                      (evil-window-increase-height 2)))
      "S-<down>"  (lambda () (interactive)
                    (if (window-in-direction 'above)
                        (evil-window-decrease-height 2)
                      (evil-window-increase-height 2))))


(map! :n "<C-tab>"   #'centaur-tabs-forward    ; normal mode only
      :n "<C-iso-lefttab>" #'centaur-tabs-backward)  ; normal mode only

(define-key evil-normal-state-map "f" 'avy-goto-char-2)
(define-key evil-normal-state-map "F" 'avy-goto-char-2)

Misc

;; Enable arrow keys in org-read-date calendar popup
(define-key org-read-date-minibuffer-local-map (kbd "<left>") (lambda () (interactive) (org-eval-in-calendar '(calendar-backward-day 1))))
(define-key org-read-date-minibuffer-local-map (kbd "<right>") (lambda () (interactive) (org-eval-in-calendar '(calendar-forward-day 1))))
(define-key org-read-date-minibuffer-local-map (kbd "<up>") (lambda () (interactive) (org-eval-in-calendar '(calendar-backward-week 1))))
(define-key org-read-date-minibuffer-local-map (kbd "<down>") (lambda () (interactive) (org-eval-in-calendar '(calendar-forward-week 1))))

;; Additional Consult bindings
(map! :leader
      (:prefix-map ("s" . "search")
       :desc "Search project" "p" #'consult-ripgrep
       :desc "Search buffer" "s" #'consult-line
       :desc "Search project files" "f" #'consult-find))

Projectile

(after! projectile
  (setq projectile-enable-caching t)
  (setq projectile-indexing-method 'hybrid))

;; Path completion
(projectile-add-known-project "~/Vaults/Writing")
(projectile-add-known-project "~/Vaults")
(projectile-add-known-project "~/go/src/github.com/jblais493/HTMXFrontend")
(projectile-add-known-project "~/go/src/github.com/jblais493/Citadel")
(projectile-add-known-project "~/Development/svelte-email")

Workspaces

;; Trying to save workspaces
(after! persp-mode
  ;; Auto-save workspaces when Emacs exits
  (setq persp-auto-save-opt 1)
  ;; Save all workspace info including window configurations
  (setq persp-set-last-persp-for-new-frames nil)
  (setq persp-reset-windows-on-nil-window-conf nil)
  ;; Load workspaces automatically on startup
  (setq persp-auto-resume-time -1))

Media and Communications

EMMS

;; EMMS
(emms-all)
(emms-default-players)
(emms-mode-line-mode 1)
(emms-playing-time-mode 1)
(setq emms-source-file-default-directory "~/Music"
      emms-browser-covers #'emms-browser-cache-thumbnail-async
      emms-browser-thumbnail-small-size 64
      emms-browser-thumbnail-medium-size 128
      emms-playlist-buffer-name "*Music*"
      emms-info-asynchronously t
      emms-source-file-directory-tree-function 'emms-source-file-directory-tree-find)
(map! :leader
      (:prefix ("m" . "music/EMMS")  ;; Changed from 'a' to 'm' for music
       :desc "Play at directory tree"   "d" #'emms-play-directory-tree
       :desc "Go to emms playlist"      "p" #'emms-playlist-mode-go
       :desc "Shuffle"                  "h" #'emms-shuffle
       :desc "Emms pause track"         "x" #'emms-pause
       :desc "Emms stop track"          "s" #'emms-stop
       :desc "Emms play previous track" "b" #'emms-previous
       :desc "Emms play next track"     "n" #'emms-next))

;; Grab album artwork for dunst to display
(defun emms-cover-art-path ()
  "Return the path of the cover art for the current track."
  (let* ((track (emms-playlist-current-selected-track))
         (path (emms-track-get track 'name))
         (dir (file-name-directory path))
         (cover-files (directory-files dir nil ".*\\(jpg\\|png\\|jpeg\\)$")))
    (when cover-files
      (concat dir (car cover-files)))))

Reading

Nov.el

;; Nov.el customizations and setup
(setq nov-unzip-program (executable-find "bsdtar")
      nov-unzip-args '("-xC" directory "-f" filename))
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))

Calibre

;; In config.el
(use-package! calibredb
  :commands calibredb
  :config
  (setq calibredb-root-dir "~/Library"
        calibredb-db-dir (expand-file-name "metadata.db" calibredb-root-dir)
        calibredb-library-alist '(("~/Library"))
        calibredb-format-all-the-icons t)

  ;; Set up key bindings for calibredb-search-mode
  (map! :map calibredb-search-mode-map
        :n "RET" #'calibredb-find-file
        :n "?" #'calibredb-dispatch
        :n "a" #'calibredb-add
        :n "d" #'calibredb-remove
        :n "j" #'calibredb-next-entry
        :n "k" #'calibredb-previous-entry
        :n "l" #'calibredb-open-file-with-default-tool
        :n "s" #'calibredb-set-metadata-dispatch
        :n "S" #'calibredb-switch-library
        :n "q" #'calibredb-search-quit))

Mu4e

;; Add mu4e to load path
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e")

;; MU4E configuration
(after! mu4e
  ;; Tell Doom where to find mu
  (setq mu4e-mu-binary "/usr/bin/mu")

  ;; Set your update interval
  (setq mu4e-update-interval (* 10 60))

  ;; Load mu4e configuration if the file exists
  (let ((mu4e-config (expand-file-name "private/mu4e-config.el" doom-private-dir)))
    (when (file-exists-p mu4e-config)
      (load mu4e-config)))
  )

Elfeed

(make-directory "~/.elfeed" t)

;; Force load elfeed-org
(require 'elfeed-org)
(elfeed-org)

;; Set org feed file
(setq rmh-elfeed-org-files '("~/.config/doom/elfeed.org"))

;; Configure elfeed
(after! elfeed
  (setq elfeed-db-directory "~/.elfeed")
  (setq elfeed-search-filter "@1-week-ago +unread -4chan -Reddit"))

(use-package! elfeed-tube
  :after elfeed
  :config
  (elfeed-tube-setup)
  :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)
         ([remap save-buffer] . elfeed-tube-save)))

Org-gcal

I use org mode for calendaring, but I export and sync to my google calendar so I have reminders on my mobile device.

;; Load private org-gcal credentials if the file exists
(let ((private-config (expand-file-name "private/org-gcal-credentials.el" doom-private-dir)))
  (when (file-exists-p private-config)
    (load private-config)))

Dirvish

;; Open dirvish
(map! :leader
      :desc "Dirvish in current dir" "d" #'dirvish)

Graphical File management

Sometimes, we need to drag and drop files to GUIs - I previously was using dragon for this, but instead setup a way to just open nautilus at the dired/dirvish location in emacs, which is a better experience

;; Open file manager in place dirvish/dired
(defun open-nautilus-here ()
  "Open nautilus in the current directory shown in dired/dirvish."
  (interactive)
  (let ((dir (cond
              ;; If we're in dired mode
              ((derived-mode-p 'dired-mode)
               default-directory)
              ;; If we're in dirvish mode (dirvish is derived from dired)
              ((and (featurep 'dirvish)
                    (derived-mode-p 'dired-mode)
                    (bound-and-true-p dirvish-directory))
               (or (bound-and-true-p dirvish-directory) default-directory))
              ;; Fallback for any other mode
              (t default-directory))))
    (message "Opening nautilus in: %s" dir)  ; Helpful for debugging
    (start-process "nautilus" nil "nautilus" dir)))

;; Bind it to Ctrl+Alt+f in both dired and dirvish modes
(with-eval-after-load 'dired
  (define-key dired-mode-map (kbd "C-M-f") 'open-nautilus-here))

;; For dirvish, we need to add our binding to its special keymap if it exists
(with-eval-after-load 'dirvish
  (if (boundp 'dirvish-mode-map)
      (define-key dirvish-mode-map (kbd "C-M-f") 'open-nautilus-here)
    ;; Alternative approach if dirvish uses a different keymap system
    (add-hook 'dirvish-mode-hook
              (lambda ()
                (local-set-key (kbd "C-M-f") 'open-nautilus-here)))))

Emacs everywhere

;; Emacs everywhere configuration
(after! emacs-everywhere
  ;; Set default frame parameters for emacs-everywhere
  (setq emacs-everywhere-frame-parameters
        '((name . "emacs-everywhere")
          (width . 80)
          (height . 24)
          (minibuffer . t)
          (menu-bar-lines . 0)
          (tool-bar-lines . 0)
          (undecorated . t))))

Custom functions and templates

I have various functions in my lisp directory for creating pomodoros, refiling done tasks to my global done.org file, and adding contacts to emails in mu4e

functions

;; lisp functions
(load! "lisp/pomodoro")
(load! "lisp/done-refile")
(load! "lisp/mu4e-contact.el")
(load! "lisp/post-to-blog")
(load! "lisp/popup-scratch")

Templates

;; Load various scripts and templates
(load! "templates/writing-template")
(load! "templates/note-template")

Legacy

Email to self at beginning of day

;;;; Send a daily email to myself with the days agenda:
;;(defun my/send-daily-agenda ()
;;  "Send daily agenda email using mu4e"
;;  (interactive)
;;  (let* ((date-string (format-time-string "%Y-%m-%d"))
;;         (subject (format "Daily Agenda: %s" (format-time-string "%A, %B %d")))
;;         (tmp-file (make-temp-file "agenda")))
;;
;;    ;; Generate agenda and save to temp file
;;    (save-window-excursion
;;      (org-agenda nil "d")
;;      (with-current-buffer org-agenda-buffer-name
;;        (org-agenda-write tmp-file)))
;;
;;    ;; Read the agenda content
;;    (let ((agenda-content
;;           (with-temp-buffer
;;             (insert-file-contents tmp-file)
;;             (buffer-string))))
;;
;;      ;; Create and send email
;;      (with-current-buffer (mu4e-compose-new)
;;        (mu4e-compose-mode)
;;        ;; Set up headers
;;        (message-goto-to)
;;        (insert "josh@joshblais.com")
;;        (message-goto-subject)
;;        (insert subject)
;;        (message-goto-body)
;;        ;; Insert the agenda content
;;        (insert agenda-content)
;;        ;; Send
;;        (message-send-and-exit)))
;;
;;    ;; Cleanup
;;    (delete-file tmp-file)))
;;
;;;; Remove any existing timer
;;(cancel-function-timers 'my/send-daily-agenda)
;;
;;;; Schedule for 5:30 AM
;;(run-at-time "05:30" 86400 #'my/send-daily-agenda)

Deft

;; Deft mode
;; (setq deft-extensions '("txt" "tex" "org"))
;; (setq deft-directory "~/Vaults/org/roam")
;; (setq deft-recursive t)
;; (setq deft-use-filename-as-title t)

Drag and drop

;; Drag and drop:
;; Function for mouse events
;;(defun my/drag-file-mouse (event)
;;  "Drag current file using dragon (mouse version)"
;;  (interactive "e")
;;  (let ((file (dired-get-filename nil t)))
;;    (when file
;;      (message "Click and drag the dragon window to your target location")
;;      (start-process "dragon" nil "/usr/local/bin/dragon"
;;                     "-x"          ; Send mode
;;                     "--keep"      ; Keep the window open
;;                     file))))
;;
;;;; Function for keyboard shortcut with multiple files support
;;(defun my/drag-file-keyboard ()
;;  "Drag marked files (or current file) using dragon"
;;  (interactive)
;;  (let ((files (or (dired-get-marked-files)
;;                   (list (dired-get-filename nil t)))))
;;    (when files
;;      (message "Click and drag the dragon window to your target location")
;;      (apply 'start-process "dragon" nil "/usr/local/bin/dragon"
;;             (append (list "-x" "--keep") files)))))
;;
;;;; Bind both versions
;;(after! dired
;;  (define-key dired-mode-map [drag-mouse-1] 'my/drag-file-mouse)
;;  (define-key dired-mode-map (kbd "C-c C-d") 'my/drag-file-keyboard))

TODO

  • Addin video previews to dirvish

As always, God bless, and until next time.

If you enjoyed this post, consider Buying me a Coffee or sending me an email