Skip to content

Latest commit

Β 

History

History
2212 lines (1962 loc) Β· 82.4 KB

config.org

File metadata and controls

2212 lines (1962 loc) Β· 82.4 KB

CONFIG

General Settings

Personal Information

;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
(setq user-full-name    "DJS"
      user-mail-address "[email protected]")

Appearance

Theme and Fonts

(setq doom-theme 'modus-vivendi
      doom-font (font-spec :family "PragmataPro Mono Liga" :size 14 :weight 'light)
      doom-variable-pitch-font (font-spec :family "Montserrat" :size 14 :weight 'light)
      +doom-dashboard-banner-dir "~/Sync/Pics/emacs"
      +doom-dashboard-banner-file "gnu-color-modus.png")

Window Borders

(modify-all-frames-parameters
 '((right-divider-width . 1)
   (internal-border-width . 1)))

Transparency

;; (set-frame-parameter nil 'alpha-background 95)
;; (add-to-list 'default-frame-alist '(alpha-background . 100))

Line Numbers and Highlighting

(remove-hook 'doom-first-buffer-hook #'global-hl-line-mode)
(setq display-line-numbers-type 'nil)
(add-hook 'org-mode-hook (lambda () (display-line-numbers-mode -1)))

Rainbow Delimiters

(setq rainbow-delimiters-max-face-count 3)

Special Punctuation Faces

(defface heavy-punctuation-face '((t (:foreground "#00d100")))
  "Face for extra emphasis on a customizable list of symbols.")
(mapc (lambda (mode)
        (font-lock-add-keywords
         mode
         '(("[;:,.#]" . 'heavy-punctuation-face))))
      '(emacs-lisp-mode
        c-mode
        lua-mode
        rjsx-mode
        typescript-ts-mode
        tsx-ts-mode
        web-mode
        gfm-mode))

column-fill

(defun my/toggle-visual-fill-column-mode ()
  "Toggle column fill mode and center text."
  (interactive)
  (if (bound-and-true-p visual-fill-column-mode)
      (progn
        (visual-fill-column-mode -1))
    (progn
      (visual-fill-column-mode)
      (setq visual-fill-column-center-text t)
      (setq fill-column 100))))

(add-hook! '(org-mode-hook

             prog-mode-hook)
           #'my/toggle-visual-fill-column-mode)

;; TODO only in org?
;; (after! visual-fill-column
;;   (setq fill-column 100))

Behavior

Trash

(setq delete-by-moving-to-trash t
      trash-directory "~/.local/share/Trash/files")

Printing

(setq printer-name "hp-neverstop")

Character Deletion

  • Prevent text deleted with x from entering kill ring
(defadvice! djs-evil-delete-char-default-to-black-hole-a (fn beg end &optional type register)
  "Advise `evil-delete-char' to set default REGISTER to the black hole register."
  :around #'evil-delete-char
  (unless register (setq register ?_))
  (funcall fn beg end type register))

Word Wrap and Visual Mode

(+global-word-wrap-mode)
(after! evil
  (evil-global-set-key 'motion "j" 'evil-next-visual-line)
  (evil-global-set-key 'motion "k" 'evil-previous-visual-line)
  (setq evil-snipe-spillover-scope 'visible))

evil-escape

(evil-escape-mode)

Scratch Buffer

(setq doom-scratch-initial-major-mode 'lisp-interaction-mode)

New Windows

(setq evil-vsplit-window-right t
      evil-split-window-below t)
  • Prompt for a buffer when creating a new window
(defadvice! prompt-for-buffer (&rest _)
  :after '(evil-window-split evil-window-vsplit)
  (consult-buffer))

which-key

(setq which-key-idle-delay .5
      which-key-allow-imprecise-window-fit nil)
(setq which-key-allow-multiple-replacements t)
(after! which-key
  (pushnew!
   which-key-replacement-alist
   '(("" . "\\`+?evil[-:]?\\(?:a-\\)?\\(.*\\)") . (nil . " \\1"))
   '(("\\`g s" . "\\`evilem--?motion-\\(.*\\)") . (nil . " \\1"))))

Marginalia

(after! marginalia
  (setq marginalia-censor-variables nil)
  (defadvice! +marginalia--anotate-local-file-colorful (cand)
    "Just a more colourful version of `marginalia--anotate-local-file'."
    :override #'marginalia--annotate-local-file
    (when-let (attrs (file-attributes (substitute-in-file-name
                                       (marginalia--full-candidate cand))
                                      'integer))
      (marginalia--fields
       ((marginalia--file-owner attrs)
        :width 12 :face 'marginalia-file-owner)
       ((marginalia--file-modes attrs))
       ((+marginalia-file-size-colorful (file-attribute-size attrs))
        :width 7)
       ((+marginalia--time-colorful (file-attribute-modification-time attrs))
        :width 12))))
  (defun +marginalia--time-colorful (time)
    (let* ((seconds (float-time (time-subtract (current-time) time)))
           (color (doom-blend
                   (face-attribute 'marginalia-date :foreground nil t)
                   (face-attribute 'marginalia-documentation :foreground nil t)
                   (/ 1.0 (log (+ 3 (/ (+ 1 seconds) 345600.0)))))))
      ;; 1 - log(3 + 1/(days + 1)) % grey
      (propertize (marginalia--time time) 'face (list :foreground color))))
  (defun +marginalia-file-size-colorful (size)
    (let* ((size-index (/ (log10 (+ 1 size)) 7.0))
           (color (if (< size-index 10000000) ; 10m
                      (doom-blend 'orange 'green size-index)
                    (doom-blend 'red 'orange (- size-index 1)))))
      (propertize (file-size-human-readable size) 'face (list :foreground color)))))

Keybinds

;; TODO combine map! calls
(map! :leader
      :desc "Doom Splash"        "k"            #'+doom-dashboard/open
      :desc "Kill buffer"        "\\"           #'kill-current-buffer
      :desc "Close window"       "DEL"          #'djs-kill-buffer-and-close-window
      :desc "Rename file" "R"                   #'doom/move-this-file
      ;; :desc "Consult jump project" ","          #'consult-jump-project
      (:prefix ("n" . "notes")
       :desc "Search ChatGPTs"        "g"       #'my/consult-ripgrep-chatgpt)
      (:prefix ("t" . "toggle")
       :desc "Comapany Mode"        "p"         #'my/toggle-company-idle-delay
       :desc "Command-logging"          "c"     #'command-log-mode
       :desc "Rainbow mode"          "R"        #'rainbow-mode
       :desc "Writegood mode"          "G"      #'writegood-mode
       ;; :desc "Indent bars"          "i"         #'indent-bars-mode
       ;; :desc "line numbers and indent bars" "L" #'my/toggle-indent-bars-and-line-numbers
       :desc "visual fill colunmn mode"     "f" #'my/toggle-visual-fill-column-mode)
      (:prefix ("o" . "open")
       :desc "Org Agenda"         "j"           #'org-launch-custom-agenda
       :desc "Command log"          "l"         #'clm/toggle-command-log-buffer
       :desc "Calendar"          "c"            #'djs-my-personal-calendar
       :desc "Treemacs peek mode" "p"           #'djs-treemacs/toggle
       :desc "Treemacs toggle" "P"              #'+treemacs/toggle
       :desc "ChatGPT" "C"                      #'gptel
       :desc "Dirvish"          "e"             #'dirvish)
      (:prefix ("s" . "search" )
       :desc "fd file" "f"                      #'my/+vertico-consult-fd))
(map! :n "[ w" #'evil-window-prev
      :n "] w" #'evil-window-next
      :n "[ TAB" #'+workspace/switch-left
      :n "] TAB" #'+workspace/switch-right)
(map! :map #'mu4e-headers-mode-map
      :n "M-+" #'mu4e-headers-mark-all-unread-read
      :n "t" #'djs-capture-msg-to-agenda
      :n "z t" #'evil-scroll-line-to-top
      :n "C-/" #'evil-ex-search-forward
      :n "S" #'mu4e-headers-mark-for-spam)
(map! :map #'org-agenda-mode-map
      :g "C-=" #'text-scale-increase
      :g "C--" #'text-scale-decrease)
(map! :map #'org-mode-map
      :n "C-j" #'outline-next-visible-heading
      :n "C-k" #'outline-previous-visible-heading
      :n "C-S-j" #'org-forward-element
      :n "C-S-k" #'org-backward-element
      :n "C-<tab>" #'org-fold-show-subtree)
(map! :map #'evil-org-mode-map
      :n "C-S-j" #'org-forward-element
      :n "C-S-k" #'org-backward-element)
(map! :map Info-mode-map
      :n "<down>" #'Info-forward-node
      :n "<up>" #'Info-backward-node
      :n "C-<down>" #'Info-next
      :n "C-<up>" #'Info-prev
      :n "<left>" #'Info-history-back
      :n "<right>" #'Info-history-forward)
(map! :map #'org-msg-edit-mode-map
      :i "<tab>" #'org-msg-tab
      )
(map! :map evil-normal-state-map "C-S-f" #'+default/search-buffer)
(map! :g "C-s" #'save-buffer)

Auth

(setq auth-sources '("~/.authinfo.gpg"))
(defun my/lookup-password (&rest keys)
  "Find a password from auth-sources and return its value as a string"
  (let ((result (apply #'auth-source-search keys)))
    (if result
        (funcall (plist-get (car result) :secret))
      nil)))
(defun my/lookup-username (&rest keys)
  "Find a login from auth-sources and return its value as a string"
  (let ((result (apply #'auth-source-search keys)))
    (if result
        (plist-get (car result) :user)
      nil)))

Lookup Providers

(setq +lookup-provider-url-alist
      '(("Doom Emacs issues" "https://github.com/hlissner/doom-emacs/issues?q=is%%3Aissue+%s")
        ("DuckDuckGo"        +lookup--online-backend-duckduckgo "https://duckduckgo.com/?q=%s")
        ("StackOverflow"     "https://stackoverflow.com/search?q=%s")
        ("Github"            "https://github.com/search?ref=simplesearch&q=%s")
        ("Youtube"           "https://youtube.com/results?aq=f&oq=&search_query=%s")
        ("MDN"               "https://developer.mozilla.org/en-US/search?q=%s")
        ("Arch Wiki"         "https://wiki.archlinux.org/index.php?search=%s&title=Special%3ASearch&wprov=acrw1")
        ("AUR"               "https://aur.archlinux.org/packages?O=0&K=%s")))

Smartparens

(after! smartparens
  (sp-local-pair 'org-mode "~" "~")
  (sp-local-pair 'org-mode "=" "="))

Calc

(after! calc
  (setq calc-algebraic-mode t))

Helper Functions

Kill Buffer and Close Window

(defun djs-kill-buffer-and-close-window ()
  "Kill the current buffer and close the window"
  (interactive)
  (kill-current-buffer)
  (+workspace/close-window-or-workspace))

Insert File Contents as String

(defun file-to-string (file)
  "File to string function"
  (with-temp-buffer
    (insert-file-contents file)
    (buffer-string)))

Flush lines

(defun remove-empty-lines ()
  "Remove all empty lines in the current buffer"
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (when (eq major-mode 'org-mode)
      (org-show-all)) ; fully expand headings in org-mode buffers
    (flush-lines "^$")))

Orderless find any file

  • Prompt for directory
(defun my/consult-fd-choose-directory ()
  "Call `+vertico/consult-fd` with a universal prefix argument"
  (interactive)
  (let ((current-prefix-arg '(4))) ; Set the universal prefix argument
    (call-interactively '+vertico/consult-fd)))
  • Search from root directory
(defun my/+vertico-consult-fd ()
  "Call `+vertico/consult-fd`from / directory.
If called with a universal argument choose a instead."
  (interactive)
  (if current-prefix-arg
      (my/consult-fd-choose-directory)
    (+vertico/consult-fd "~/")))

Modules

app

everywhere

(after! emacs-everywhere
  (setq emacs-everywhere-major-mode-function #'org-mode)
  (add-to-list 'emacs-everywhere-markdown-apps "Brave")
  (add-to-list 'emacs-everywhere-markdown-apps "Firefox"))

Completion

company

(after! company
  (setq company-idle-delay              nil
        company-tooltip-idle-delay      0
        company-tooltip-maximum-width      60
        company-tooltip-width-grow-only      t
        company-minimum-prefix-length   0
        company-show-quick-access       t
        company-global-modes '(eshell-mode elisp-mode))

  (map! :map company-active-map
        :g "<tab>" nil
        :g "TAB" nil
        :g "C-S-j" #'company-next-page
        :g "C-S-k" #'company-previous-page)

(map! :g "C-S-p" #'+company/complete)

  (defun my/toggle-company-idle-delay ()
    "Toggle the value of `company-idle-delay' between 0 and nil.
Enable or disable `company-mode' accordingly."
    (interactive)
    (if (or (eq company-idle-delay nil)
            (not company-mode))
        (progn
          (setq company-idle-delay .2)
          (company-mode 1)
          (message "company-mode enabled"))
      (progn
        (setq company-idle-delay nil)
        (company-mode -1)
        (message "company-mode disabled")))))
  • TODO figure out how to get this working in a hook
(defun my/set-company-js-backends ()
  (interactive)
  (setq-local company-backends
              '((:separate company-capf company-files ))))
  • Company Box
    (after! company-box
      (setq company-box-doc-frame-parameters '((internal-border-width . 1))))
        

copilot

;; accept completion from copilot and fallback to company
(use-package! copilot
  :defer t
  :hook (prog-mode . copilot-mode)
  :bind (:map copilot-completion-map
              ("C-<return>" . 'copilot-accept-completion)
              ("C-RET" . 'copilot-accept-completion)
              ("C-TAB" . 'copilot-accept-completion-by-word)
              ("C-<tab>" . 'copilot-accept-completion-by-word)))

vertico

Keybinds

(map! :map #'evil-normal-state-map
      (:prefix ("g" . "search" )
       :g "/" #'avy-goto-char-timer))
  • TODO Figure out why ~~+vertico-consult-fd-args~ seem not to affect +vertico/consult-fd results, and I have to resort to changing consult-find-args to achieve the desired result instead. Am I even using fd anymore?
(setq consult-find-args  "find .")
  • Remove consult buffer and file selections that I’d rather find by other means like iBuffer or consult-recent-file
(after! consult
(setq consult-buffer-filter '("\\*")
;; consult-project-buffer-sources '(consult--source-project-buffer)
))

Default Behavior

(after! avy
  (setq avy-all-windows 'all-frames))

Consult

(setq consult-ripgrep-args "rg --null --hidden --line-buffered --color=never --max-columns=1000 --path-separator /   --smart-case --no-heading --with-filename --line-number --search-zip")

Checkers

spell

;; TODO figure out what's causing some words to highlight even when spelled correctly
(after! flyspell
  (setq flyspell-duplicate-distance 0))

Emacs

dired/dirvish

Prevent hordes of dired buffers from piling up over time

(after! dired
  (setq dired-kill-when-opening-new-dired-buffer t))
;; TODO Look into enabling dirvish through Doom's modules
(dirvish-override-dired-mode)
(remove-hook! 'dired-mode-hook 'diff-hl-dired-mode-unless-remote)
(map! :map dired-mode-map :ng "q" #'dirvish-quit)
(setq dired-mouse-drag-files t)                   ; added in Emacs 29
(setq mouse-drag-and-drop-region-cross-program t) ; added in Emacs 29
(defun my/dired-toggle-hidden ()
  "toggle hidden files and directories"
  (interactive)
  (if (string-equal dired-listing-switches "-hl -v --group-directories-first")
      (setq dired-listing-switches "-ahl -v --group-directories-first")
    (setq dired-listing-switches "-hl -v --group-directories-first")))
(after! dirvish
  (setq dirvish-attributes '(vc-state
                             subtree-state
                             nerd-icons
                             collapse
                             git-msg
                             file-time
                             file-size)
        dirvish-emerge-groups '(("Recent"
                                 (predicate . recent-files-2h))
                                ("README"
                                 (regex . "README")))
        dirvish-default-layout '(0 0.20 0.67 )
        dired-listing-switches "-ahl -v --group-directories-first"
        dirvish-show-media-properties t
        dirvish-reuse-session nil
        dirvish-emerge-groups `(("Recent" (predicate . recent-files-2h))
                                ("README" (regex . "README"))
                                ("Org" (extensions  "org"))
                                ("LaTeX"  (extensions "tex" "bib"))
                                ("PDF"    (extensions "pdf"))
                                ("Videos" (extensions  ,@dirvish-video-exts))
                                ("Audio" (extensions  ,@dirvish-audio-exts))
                                ("Images" (extensions  ,@dirvish-image-exts))
                                ("Word" (extensions  "doc" "docx"))
                                ("Excel" (extensions  "xls" "xlsx"))
                                ("Powerpoint" (extensions  "ppt" "pptx"))
                                ("Archive" (extensions  "tar" "tar.gz" "zip" "rar" "7z"))
                                ("Hidden" (regex .  "^\\..*"))
                                ("Directories" (predicate . directories ))
                                ("Files" (predicate . files ))
                                ("Executables" (predicate . executables ))
                                ))
  (map! :map #'evil-normal-state-map
        (:prefix ("g" . "search" )
         :g "S" #'dirvish-quick-access))

  (map! :map #'dired-mode-map
        :n "s" #'dirvish-quick-access
        :n "TAB" #' dirvish-emerge-toggle-current-group
        :n "C-j" #'dirvish-emerge-next-group
        :n "C-k" #'dirvish-emerge-previous-group)
  (dirvish-define-preview exa (file)
    "Use `exa' to generate directory preview."
    :require ("exa") ; tell Dirvish to check if we have the executable
    (when (file-directory-p file) ; we only interest in directories here
      `(shell . ("exa" "-alH" "--color=always" "--icons"
                 "--group-directories-first" ,file))))

  (add-to-list 'dirvish-preview-dispatchers 'exa)
  ;; HACK Start the "Hidden" emerge group folded
  (defun my/dirvish-toggle-hidden-emerge-group ()
    "Toggle Hidden emerge group"
    (interactive)
    (save-excursion
      (goto-char (point-min))
      (when (re-search-forward "^[[:space:]]*Hidden[[:space:]]*$" nil t)
        (call-interactively 'dirvish-emerge-toggle-current-group))))
  (add-hook! 'dirvish-setup-hook '(dirvish-emerge-mode
                                   my/dirvish-toggle-hidden-emerge-group)))
(use-package dirvish
  :custom
  (dirvish-quick-access-entries ; It's a custom option, `setq' won't work
   '(("h" "~/"                               "Home")
     ("r" "/"                                "Root")
     ("C" "~/.config"                      "Config")
     ("d" "~/Downloads/"                "Downloads")
     ("D" "~/Desktop/"                    "Desktop")
     ("o" "~/Sync/projects/org/"              "Org")
     ("c" "~/Sync/projects/org/clients/"  "Clients")
     ("s" "~/Sync"                           "Sync")
     ("g" "~/Github"                       "Github")
     ("p" "~/Pictures"                   "Pictures")
     ("v" "~/Videos"                       "Videos")
     ("m" "~/Motherhsip"                     "Media")
     ("M" "~/Sync/memes"                     "Memes")
     ("t" "~/.local/share/Trash/files/"     "Trash"))))

Tools

lsp

  • Disable auto formatting with lsp to prevent interference with tools like prettier
(after! lsp-mode
  (setq +format-with-lsp nil))
  • Make sure certain language servers are always available
(after! lsp-mode
  (lsp-ensure-server 'ts-ls)
  (lsp-ensure-server 'bash-ls)
  (lsp-ensure-server 'emmet-ls)
  (lsp-ensure-server 'html-ls)
  (lsp-ensure-server 'dockerfile-ls)
  (lsp-ensure-server 'yamlls)
  (lsp-ensure-server 'json-ls)
  (lsp-ensure-server 'eslint)
  (lsp-ensure-server 'css-ls)
  (lsp-ensure-server 'clangd)
  (lsp-ensure-server 'tailwindcss)
  (lsp-ensure-server 'lua-language-server))
  • Enable linting for tailwindcss
(use-package! lsp-tailwindcss
  :defer t
  :init
  (setq lsp-tailwindcss-add-on-mode t))
  • Match major modes to file extensions
(with-eval-after-load 'lsp-mode (add-to-list 'lsp-language-id-configuration
                                             '(web-mode . "scss"))
                      (add-to-list 'lsp-disabled-clients 'flow-ls))

biblio (citar)

(after! citar
  (setq citar-bibliography '("~/Sync/Refs/My Library.bib")
         citar-org-roam-subdir "literature-notes"
         citar-notes-paths '("~/Sync/projects/org/roam/literature-notes")
         ;; TODO Read this template from a file (insert-file-contents?)
         citar-org-roam-note-title-template "${author} - ${title}\n#+filetags: :literature: \n* TODOs\n:PROPERTIES:\n:CATEGORY: ${author}\n:END:\n* Notes\n* Takeaways\n* Practices\n* Quotes\n* Thoughts"
         ;; TODO Determine if this block is necessary to prevent double insertion of "#+title:"
         citar-templates '((main . "${author editor:30}     ${date year issued:4}     ${title:48}")
                           (suffix . "          ${=key= id:15}    ${=type=:12}    ${tags keywords keywords:*}")
                           (preview . "${author editor} (${year issued date}) ${title}, ${journal journaltitle publisher container-title collection-title}.\n")
                           (note . "${author} - ${title}"))))

rgb

  • Prevent ordinary words (e.g β€œred”, β€œblue”, etc.) from highlight in rainbow mode
(add-hook 'rainbow-mode-hook
          (defun rainbow-turn-off-words ()
            "Turn off word colours in rainbow-mode."
            (interactive)
            (font-lock-remove-keywords
             nil
             `(,@rainbow-x-colors-font-lock-keywords
               ,@rainbow-latex-rgb-colors-font-lock-keywords
               ,@rainbow-r-colors-font-lock-keywords
               ,@rainbow-html-colors-font-lock-keywords
               ,@rainbow-html-rgb-colors-font-lock-keywords))))

tree-sitter

use treesit-auto

(use-package treesit-auto
  :config
  (global-treesit-auto-mode))

tsx-ts-mode

Set languages to mode and hook into lsp
  • TODO See if we can clean this up. It seems like this is a hack. Should treesit-auto do this?
(after! tree-sitter
  (setq +tree-sitter-hl-enabled-modes t))

(after! tree-sitter-langs
  (add-to-list 'tree-sitter-major-mode-language-alist '(tsx-ts-mode . tsx)))

(after! tree-sitter-langs
  (add-to-list 'tree-sitter-major-mode-language-alist '(typescript-ts-mode . typescript)))

(use-package typescript-ts-mode
  :mode (("\\.ts\\'" . typescript-ts-mode)
         ("\\.tsx\\'" . tsx-ts-mode))
  :config
  (add-hook! '(typescript-ts-mode-hook tsx-ts-mode-hook) #'lsp!))
Configure emmet-mode, rjsx-minor-mode, and tree-sitter-hl-mode
(add-hook! 'tsx-ts-mode-hook
           #'emmet-mode
           #'rjsx-minor-mode
           #'tree-sitter-hl-mode)

(add-hook! 'emmet-mode-hook '(lambda ()
                               (add-to-list 'emmet-jsx-major-modes 'tsx-ts-mode)))

(map! :map tsx-ts-mode-map
      :nvi "C-c C-j" #'rjsx-jump-tag
      :nvi "C-c C-r" #'rjsx-rename-tag-at-point
      :nvi "<" #'rjsx-electric-lt
      :nvi ">" #'rjsx-electric-gt)
Format on save with format-all
;; (after! format-all
;;   (puthash 'tsx-ts-mode
;;            '((prettier closure (t) nil "typescript"))
;;            format-all--mode-table))

;; (after! format-all
;;   (puthash 'typescript-ts-mode
;;            '((prettier closure (t) nil "typescript"))
;;            format-all--mode-table))

;; (after! format-all
;;   (puthash 'typescript-ts-base-mode
;;            '((prettier closure (t) nil "typescript"))
;;            format-all--mode-table))
Set company backends
(add-hook! '(typescript-ts-mode-hook tsx-ts-mode-hook )
           #'my/set-tsx-ts-mode-company-backends)
(defun my/set-tsx-ts-mode-company-backends ()
  (interactive)
  (company-mode -1)
  (company-mode 1)
  (setq-local company-backends
        '(company-capf)))

UI

doom-dashboard

(defadvice! close-doom-windows-after-gptel (&rest /)
  :after #'gptel
  (let ((doom-window (get-buffer-window "*doom*")))
    (when (and doom-window (memq doom-window (window-list)))
      (delete-window doom-window))))
(setq +doom-dashboard-functions
      '(doom-dashboard-widget-banner
        ;; doom-dashboard-widget-shortmenu
        my/doom-dashboard-widget-footer
        ;; doom-dashboard-widget-loaded
        ))
(setq +doom-dashboard-banner-padding '(0 . 1))
(setq +doom-dashboard-menu-sections
      '(("Agenda" :icon
         (all-the-icons-octicon "checklist" :face 'doom-dashboard-menu-title)
         :when (fboundp 'org-launch-custom-agenda)
         :action org-launch-custom-agenda)
        ("Calendar" :icon
         (all-the-icons-octicon "calendar" :face 'doom-dashboard-menu-title)
         :when (fboundp 'djs-my-personal-calendar)
         :action djs-my-personal-calendar)
        ("Terminal" :icon
         (all-the-icons-octicon "terminal" :face 'doom-dashboard-menu-title)
         :action +vterm/here)
        ("Mail" :icon
         (all-the-icons-octicon "mail" :face 'doom-dashboard-menu-title)
         :action =mu4e)
        ("ChatGPT" :icon
         (all-the-icons-octicon "light-bulb" :face 'doom-dashboard-menu-title)
         :action gptel)))
(defun my/doom-dashboard-widget-footer ()
    (insert
     "\n\n"
     (propertize
      (+doom-dashboard--center
       +doom-dashboard--width
       "Welcome Back...")
      'face 'doom-dashboard-loaded)
     "\n"))

hl-todo

  • Set some preferred colors for highlighting todo items
(after! hl-todo
  (setq  hl-todo-keyword-faces
          '(("TODO" . "#fdb900")
          ("PROG" .  "#93e079")
          ("WAIT" .  "#569cd6")
          ("HOLD" .  "#a9a5aa")
          ("SHOP" .  "#c586c0")
          ("IDEA" .  "#93e079")
          ("BUG" . "#ff8059")
          ("DONE" . "#5B6268")
          ("NOTE" . "#d3b55f")
          ("HACK" . "#d0bc00")
          ("TEMP" . "#ffcccc")
          ("FIXME" . "#ff9077")
          ("REVIEW" . "#6ae4b9")
          ("EDIT" . "#fdb900")
          ("DEPRECATED" . "#bfd9ff"))))

nav-flash

(add-hook! 'evil-jumps-post-jump-hook #'+nav-flash-blink-cursor-maybe-h)

popup

  • Keep certain windows hanging around longer than Doom’s defaults
(set-popup-rules!
  '(("^\\*info\\*"                   :ignore t)
    ;; ("^\\*format-all-errors\\*"      :select t)
    ("^\\*Man"                       :ignore t)))

tabs

;; (after! centaur-tabs
;;   (centaur-tabs-group-by-projectile-project)
;;   (setq centaur-tabs-style "bar"
;;         centaur-tabs-set-bar 'under
;;         centaur-tabs-label-fixed-length 12))
;; (add-hook! ('cfw:calendar-mode-hook
;;             'mu4e-main-mode-hook
;;             'mu4e-headers-mode-hook
;;             'mu4e-view-mode-hook
;;             'org-msg-edit-mode-hook
;;             'org-agenda-mode-hook
;;             'magit-select-mode-hook
;;             'magit-log-select-mode-hook
;;             'magit-log-mode-hook
;;             'git-commit-mode-hook
;;             'magit-diff-mode-hook
;;             '+doom-dashboard-mode-hook)
;;            #'centaur-tabs-local-mode)
;; (add-hook! 'doom-after-init-hook #'centaur-tabs-mode)

treemacs

  • TODO Find out why I can’t get treemacs files in fixed-pitch
(setq doom-themes-treemacs-theme        'doom-colors
      +treemacs-git-mode                'extended
      doom-themes-treemacs-enable-variable-pitch nil)
  • Sensibly launch treemacs in peek-mode
(defun djs-treemacs-peek-mode ()
  "Custom function to launch treemacs for the current file in peek-mode"
  (interactive)
  (treemacs-find-file)
  (treemacs-select-window)
  (treemacs-peek-mode)
  (treemacs-fit-window-width))
  • Clone toggle function to launch in peek-mode
(defun djs-treemacs/toggle ()
  "Initialize or toggle treemacs in peek mode."
  (interactive)
  (require 'treemacs)
  (pcase (treemacs-current-visibility)
    (`visible (delete-window (treemacs-get-local-window)))
    (_ (if (doom-project-p)
           (djs-treemacs-peek-mode)
         (treemacs)))))

Workspaces

(after! persp-mode
  (setq persp-emacsclient-init-frame-behaviour-override -1))

Editor

Format

;; (setq +format-on-save-enabled-modes
;;   (append +format-on-save-enabled-modes '(org-mode)))

Lang

web

  • Set the file extensions to open in web-mode
(add-hook! 'web-mode-hook
           #'rainbow-delimiters-mode-enable)
(add-to-list 'auto-mode-alist '("\\.html$" . web-mode))
(add-to-list 'auto-mode-alist '("\\.css$"  . web-mode))
(add-to-list 'auto-mode-alist '("\\.scss$" . web-mode))
  (setq web-mode-skip-fontification 't)

lua

  • Enable rainbow delimiters mode (not sure why this isn’t default)
(add-hook! '(lua-mode-hook
             tsx-ts-mode-hook)
           #'rainbow-delimiters-mode-enable)

Org

org-mode

Paths and Default Settings

  • Set default paths and customize org-mode-hook
  • Enable auto-revert-mode for org buffers to facilitate syncthing more conveniently
(after! org
  (setq
   org-directory "~/Sync/projects/org/"
   org-attach-directory "~/Sync/projects/org/.attach/"
   +org-capture-emails-file "todo.org"
   ;; workaround to get diary date formats into cfw-cal
   diary-file "~/Sync/projects/org/calendars/birthdays-anniversaries.org"
   ;; org-startup-indented nil
   org-hide-emphasis-markers t
   org-startup-folded 'show2levels
   org-ellipsis ".."
   org-default-priority 67
   org-lowest-priority 67
   org-image-actual-width 600
   org-attach-auto-tag nil
   org-log-into-drawer "LOGBOOK"
   org-duration-format (quote h:mm)))
(add-hook! 'org-mode-hook #'auto-revert-mode)
;; (add-hook! 'org-mode-hook #'mixed-pitch-mode)

β€˜TODO’ Behavior

  • Automatically complete a parent todo when all subentries are completed.
(after! org
  (defun org-summary-todo (n-done n-not-done)
    "Switch entry to DONE when all subentries are done, to TODO otherwise."
    (let (org-log-done org-log-states)   ; turn off logging
      (org-todo (if (= n-not-done 0) "DONE" "[ ]"))))
  (add-hook 'org-after-todo-statistics-hook #'org-summary-todo))
(after! org
  (defun org-todo-with-date (&optional arg)
    (interactive "P")
    (cl-letf* ((org-read-date-prefer-future nil)
               (my-current-time (org-read-date t t nil "when:" nil nil nil))
               ((symbol-function 'current-time)
                #'(lambda () my-current-time))
               ((symbol-function 'org-today)
                #'(lambda () (time-to-days my-current-time)))
               ((symbol-function 'org-current-effective-time)
                #'(lambda () my-current-time))

               (super-org-entry-put (symbol-function 'org-entry-put))
               ((symbol-function 'org-entry-put)
                #'(lambda (pom property value)
                    (print property)
                    (if (equal property "LAST_REPEAT")
                        (let ((my-value (format-time-string (org-time-stamp-format t t) my-current-time)))
                          (funcall super-org-entry-put pom property my-value))
                      (funcall super-org-entry-put pom property value)
                      ))))
      (if (eq major-mode 'org-agenda-mode) (org-agenda-todo arg) (org-todo arg)))))
  • Let us refile only a region within a tree (credit this Stack Overflow post)
(after! org
  (defvar org-refile-region-format "\n%s\n")
  (defvar org-refile-region-position 'top
    "Where to refile a region. Use 'bottom to refile at the
end of the subtree. ")
  (defun org-refile-region (beg end copy)
    "Refile the active region.
If no region is active, refile the current paragraph.
With prefix arg C-u, copy region instad of killing it."
    (interactive "r\nP")
    ;; mark paragraph if no region is set
    (unless (use-region-p)
      (setq beg (save-excursion
                  (backward-paragraph)
                  (skip-chars-forward "\n\t ")
                  (point))
            end (save-excursion
                  (forward-paragraph)
                  (skip-chars-backward "\n\t ")
                  (point))))
    (let* ((target (save-excursion (org-refile-get-location)))
           (file (nth 1 target))
           (pos (nth 3 target))
           (text (buffer-substring-no-properties beg end)))
      (unless copy (kill-region beg end))
      (deactivate-mark)
      (with-current-buffer (find-file-noselect file)
        (save-excursion
          (goto-char pos)
          (if (eql org-refile-region-position 'bottom)
              (org-end-of-subtree)
            (org-end-of-meta-data))
          (insert (format org-refile-region-format text)))))))

org-modern

(global-org-modern-mode)
(after! org-modern
  (setq
   org-modern-checkbox '((?\s . "TODO"))
   org-modern-todo-faces '(("TODO" :foreground "#fdb900")
                           ("PROG" :foreground "#93e079")
                           ("WAIT" :foreground "#569cd6")
                           ("HOLD" :foreground "#a9a5aa")
                           ("[ ]" :foreground "#fdb900")
                           ("[-]" :foreground "#93e079")
                           ("[?]" :foreground "#569cd6")
                           ("[~]" :foreground "#a9a5aa")
                           ("SHOP" :foreground "#c586c0")
                           ("IDEA" :foreground "#93e079")))
  ;; (defun my/org-modern-set-star-based-on-theme ()
  ;;   "set the value of org-modern-star based on the current theme"
  ;;   (if (or (eq doom-theme 'doom-bisqwit)
  ;;           (eq doom-theme 'doom-tibetan))
  ;;       (setq org-modern-star '("⚘" "✿" "❁" "✾" "❀" "✀"))
  ;;     (setq org-modern-star 'nil)))
  ;; (add-hook! 'doom-load-theme-hook #'my/org-modern-set-star-based-on-theme)
  (add-hook! 'org-modern-mode-hook #'hl-todo-mode))

fancy-priorities

(after! org-fancy-priorities
  (setq org-fancy-priorities-list '( "πŸ”₯" "❗" "πŸ’€" )))

Custom todo-keywords

(after! org
  (setq org-todo-keywords
        '((sequence "TODO(t)"
           "PROG(p)"
           "WAIT(w)"
           "HOLD(h)"
           "|"
           "DONE(d)")
          (sequence "[ ](T)"
                    "[-](P)"
                    "[?](W)"
                    "[~](H)"
                    "|"
                    "[X](D)")
          (sequence "SHOP(s)"
                    "IDEA(i)"
                    "|"
                    "DONE(d)"))))

Capture

Templates

(after! org
  (defun my/format-org-capture-link ()
    "Format the captured contents to a headling friendly link string."
    (let ((annotation (substring (substring-no-properties (plist-get org-store-link-plist :initial)) 0 30)))
      (substring
       (replace-regexp-in-string " +" " "
                                 (replace-regexp-in-string "\n" " "
                                                           annotation))2 -2 )))
  (setq
   org-capture-templates
   ;; Personal Todo Templates
   ;; TODO figure out how to use %i inside %(sexp) to prevent prefixes when capturing a multi-line region
   `(("t" "βœ… Todo")
     ("tp" "♉ Personal"
      entry (file+headline "todo.org" "♉ Personal")
      "* TODO %?"
      :kill-buffer t)
     ("ta" "🐍 Animals"
      entry (file+headline "todo.org" "🐍 Animals")
      "* TODO %?"
      :kill-buffer t)
     ("ts" "πŸ›’ Shopping List"
      entry (file+headline "todo.org" "πŸ›’ Shopping")
      "* SHOP %?"
      :kill-buffer t)
     ("th" "🏑 Home"
      entry (file+headline "todo.org" "🏑 Home")
      "* TODO %?"
      :kill-buffer t)
     ("to" "πŸ’» Office"
      entry (file+headline "todo.org" "πŸ’» Office")
      "* TODO %?"
      :kill-buffer t)
     ("tP" "✈ POA"
      entry (file+headline "todo.org" "✈ POA")
      "* TODO %?"
      :kill-buffer t)
     ("tl" "πŸ’» LTP"
      entry (file+headline "todo.org" "πŸ’» LTP")
      "* TODO %?"
      :kill-buffer t)
     ("tm" "⁉ Misc."
      entry (file+headline "todo.org" "⁉ Inbox")
      "* TODO %?"
      :kill-buffer t)
     ("a" "πŸ“… Appointment"
      entry (file+headline "appt.org" "Inbox")
      "* %?\n<%(org-read-date)>"
      :kill-buffer t)
     ("n" "πŸ“₯ Note"
      entry (file+headline "notes.org" "πŸ“₯ Inbox") ,
      "* %?[[file:%F::%(my/format-org-capture-link)][%f]] - %U \n\n#+begin_src \n%i#+end_src\n\n[[file:%F::%(my/format-org-capture-link)][visit file]]")
     ;; Default cenralized project templates
     ("g" "🌏 Global Project Files")
     ("gt" "βœ… Project todo"
      entry #'+org-capture-central-project-todo-file
      "* TODO %?[[file:%F::%(my/format-org-capture-link)][%f]] - %U \n\n#+begin_src \n%i#+end_src\n\n[[file:%F::%(my/format-org-capture-link)][visit file]]"
      :heading "Tasks"
      :prepend nil
      :kill-buffer t)
     ("gn" "✏ Project notes"
      entry #'+org-capture-central-project-notes-file
      "* %?[[file:%F::file:%F::%(my/format-org-capture-link)][%f]] - %U \n\n#+begin_src \n%i#+end_src\n\n[[file:%F::%(my/format-org-capture-link)][visit file]]"
      :heading "Notes"
      :prepend nil
      :kill-buffer t)
     ("gc" "🏁 Project changelog"
      entry #'+org-capture-central-project-changelog-file
      "* %?[[file:%F::%(my/format-org-capture-link)][%f]] - %U \n\n#+begin_src \n%i#+end_src\n\n[[file:%F::%(my/format-org-capture-link)][visit file]]"
      :heading "Changelog"
      :prepend nil
      :kill-buffer t)
     ;; Default local project templates
     ("l" "πŸ”’ Local Project Files")
     ("lt" "βœ… Project-local todo"
      entry (file+headline +org-capture-project-todo-file "Inbox")
      "* TODO %?[[file:%F::%(my/format-org-capture-link)][%f]] - %U \n\n#+begin_src \n%i#+end_src\n\n[[file:%F::%(my/format-org-capture-link)][visit file]]"
      :prepend nil
      :kill-buffer t)
     ("ln" "✏ Project-local notes"
      entry (file+headline +org-capture-project-notes-file "Inbox")
      "* %?[[file:%F::%(my/format-org-capture-link)][%f]] - %U \n\n#+begin_src \n%i#+end_src\n\n[[file:%F::%(my/format-org-capture-link)][visit file]]"
      :prepend nil
      :kill-buffer t)
     ("lc" "🏁 Project-local changelog"
      entry (file+headline +org-capture-project-changelog-file "Unreleased")
      "* %?[[file:%F::%(my/format-org-capture-link)][%f]] - %U \n\n#+begin_src \n%i#+end_src\n\n[[file:%F::%(my/format-org-capture-link)][visit file]]"
      :prepend nil
      :kill-buffer t))))

Email

  • Clone +mu4e/capture-msg-to-agenda to modify the timestamp behavior and default heading
  • TODO fix universal argument for deadline
(defun djs-capture-msg-to-agenda (arg)
  "Refile a message and add a entry in `+org-capture-emails-file' with no deadline. With one prefix, deadline
is today.  With two prefixes, select the deadline. Afterwards save the todo file and reload the agenda if it's open"
  (interactive "p")
  (let ((sec "^* πŸ“§ Email")
        (msg (mu4e-message-at-point)))
    (when msg
      ;; put the message in the agenda
      (with-current-buffer (find-file-noselect
                            (expand-file-name +org-capture-emails-file org-directory))
        (save-excursion
          ;; find header section
          (goto-char (point-min))
          (when (re-search-forward sec nil t)
            (let (org-M-RET-may-split-line
                  (lev (org-outline-level))
                  (folded-p (invisible-p (point-at-eol)))
                  (from (plist-get msg :from)))
              (when (consp (car from)) ; Occurs when using mu4e 1.8+.
                (setq from (car from)))
              (unless (keywordp (car from)) ; If using mu4e <= 1.6.
                (setq from (list :name (or (caar from) (cdar from)))))
              ;; place the subheader
              (when folded-p (show-branches))    ; unfold if necessary
              (org-end-of-meta-data) ; skip property drawer
              (org-insert-todo-heading 1)        ; insert a todo heading
              (when (= (org-outline-level) lev)  ; demote if necessary
                (org-do-demote))
              ;; insert message and add deadline
              (insert (concat " [[mu4e:msgid:"
                              (plist-get msg :message-id) "]["
                              (truncate-string-to-width
                               (plist-get from :name) 25 nil nil t)
                              " - "
                              (truncate-string-to-width
                               (plist-get msg :subject) 40 nil nil t)
                              "]] "))
              (cond ((= arg 4) (org-deadline nil (format-time-string "%Y-%m-%d")))
                    ((= arg 1) nil)
                    ((org-deadline nil nil)))
              (org-update-parent-todo-statistics)
              ;; refold as necessary
              (if folded-p
                  (progn
                    (org-up-heading-safe)
                    (hide-subtree))
                (hide-entry))))))
      ;; refile the message and update
      ;; (cond ((eq major-mode 'mu4e-view-mode)
      ;;        (mu4e-view-mark-for-refile))
      ;;       ((eq major-mode 'mu4e-headers-mode)
      ;;        (mu4e-headers-mark-for-refile)))
      (message "Refiled and added to the agenda.")))
  (with-current-buffer "todo.org"
        (save-buffer)))

Archiving

  • Create a function to archive all completed tasks in a file (from this stack overflow post)
  • TODO find out how to recreate subtree in lambda expression so as not to rely on :ARCHIVE: property
(after! org
  (defun org-archive-done-tasks ()
    "Archive all tasks marked DONE in the file."
    (interactive)
    (org-map-entries
     (lambda ()
       (org-archive-subtree)
       (setq org-map-continue-from (org-element-property :begin (org-element-at-point))))
     "/DONE" 'file)))
(after! org (defun my/reload-agenda-if-open ()
              "Reload the org agenda if the buffer exists"
              (if (get-buffer "*Org Agenda*")
                  (with-current-buffer "*Org Agenda*"
                    (org-launch-custom-agenda))))
  (defun my/reload-agenda-on-save-org-file ()
    "Reload the org agenda if the file saved is an org file"
    (if (string= (file-name-extension (buffer-file-name)) "org")
        (my/reload-agenda-if-open)))
  (add-hook 'after-save-hook #'my/reload-agenda-on-save-org-file)
  (add-hook 'after-revert-hook #'my/reload-agenda-if-open))

org-roam

Paths and Default Settings

(after! org-roam
  (setq +org-roam-auto-backlinks-buffer t
        org-roam-directory (concat org-directory "roam/")
        org-roam-db-location (concat org-roam-directory ".org-roam.db")
        org-roam-dailies-directory "journal/"))

Capture

  • TODO defun to grab annotation and format
(after! org-roam
  (setq org-roam-capture-templates
        `(("f" "⏳ Fleeting" plain
           ,(format "#+title: Fleeting - ${title} - %s\n#+filetags: :fleeting:\n*" "%i" "${title}\n%%[%s/template/fleeting.org]" "%T" org-roam-directory)
           :target (file "inbox/fleeting_${slug}_%<%Y%m%d%H%M%S>.org" )
           :kill-buffer t)
          ("z" "πŸ’­ Zettel" plain
           ,(format "#+title: ${title}\n* ${title}\n%%[%s/template/zettel.org]" org-roam-directory)
           :target (file "zettels/zettel_${slug}.org")
           :kill-buffer t)
          ("e" "πŸ’ͺ Exercise" plain
           ,(format "#+title: ${title}\n#+filetags: :exercise:\n* ${title}\n%%[%s/template/exercise.org]" org-roam-directory)
           :target (file "exercises/exercises_${slug}.org")
           :kill-buffer t)
          ("a" "🀸 Asana" plain
           ,(format "#+title: ${title}\n#+filetags: :yoga:\n* ${title}\n%%[%s/template/asana.org]" org-roam-directory)
           :target (file "asanas/asana_${slug}.org")
           :kill-buffer t)
          ("c" "πŸ’» Command" plain
           ,(format "#+title: ${title}\n#+filetags: :command_line:\n* ${title}\n%%[%s/template/asana.org]" org-roam-directory)
           :target (file "commands/command_${slug}.org")
           :kill-buffer t))
        org-roam-dailies-capture-templates
        '(("a" "πŸ“… Agenda" entry
           ;; TODO Use path expansion for templates
           ;; TODO Ensure templates to properly add tags when not invoked to create file
           (file "~/Sync/projects/org/roam/template/agenda.org")
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%A %B %d, %Y>\n#+filetags: daily")
           :kill-buffer t)
          ("d" "πŸ’€ Dream" entry "* πŸ’€ Dream\n%?"
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%A %B %d, %Y>\n#+filetags: :daily:dream:")
           :kill-buffer t)
          ("g" "🏌 Golf" entry "* 🏌 Golf\n%?"
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%A %B %d, %Y>\n#+filetags: :daily:golf:")
           :kill-buffer t)
          ("t" "πŸ’­ Thought" entry "* πŸ’­ Thought %<%H:%M> \n%?"
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%A %B %d, %Y>\n#+filetags: :daily:thought:")
           :kill-buffer t)
          ("w" "πŸ’ͺ Workout" entry "* πŸ’ͺ Workout \n** Warm-up\n*** [ ] %?\n** Main Circuit\n*** [ ]\n** Cool down\n*** [ ]"
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%A %B %d, %Y>\n#+filetags: :daily:workout:")
           :kill-buffer t))))

org-agenda

Customize Appearance

(add-hook! 'org-agenda-mode-hook #'my/disable-word-wrap-mode)

(defun my/disable-word-wrap-mode ()
  (+word-wrap-mode -1))

Set Agenda Files

  • Grab the most recent org-roam daily and set the list of agenda files
  • TODO find a cleaner way to set org-agenda-files
  • TODO find a way to populate roam-extra:todo-files with SQL
(after! org
  (defun djs-org-agenda-files ()
    "Add selected files to `org-agenda-files`."
    (setq org-agenda-files
          '("~/Sync/projects/org"
            "~/Sync/projects/org/calendars"
            ;; "~/Sync/projects/org/roam/literature-notes"
            "~/Sync/projects/org/hide-from-orgzly"))
    (setq org-agenda-files
          (append org-agenda-files (roam-extra:todo-files)))
    (setq org-refile-targets '((org-agenda-files :maxlevel . 3))))

  ;; Hook the function into both org and org-agenda mode
  (add-hook! '(org-mode-hook org-agenda-mode-hook) #'djs-org-agenda-files))
(after! org-roam
  (defun roam-extra:get-filetags ()
    (split-string (or (org-roam-get-keyword "filetags") "")))
  (defun roam-extra:add-filetag (tag)
    (let* ((new-tags (cons tag (roam-extra:get-filetags)))
           (new-tags-str (combine-and-quote-strings new-tags)))
      (org-roam-set-keyword "filetags" new-tags-str)))
  (defun roam-extra:del-filetag (tag)
    (let* ((new-tags (seq-difference (roam-extra:get-filetags) `(,tag)))
           (new-tags-str (combine-and-quote-strings new-tags)))
      (org-roam-set-keyword "filetags" new-tags-str)))
  (defun roam-extra:todo-p ()
    "Return non-nil if current buffer has any TODO entry.
TODO entries marked as done are ignored, meaning the this
function returns nil if current buffer contains only completed
tasks."
    (org-element-map
        (org-element-parse-buffer 'headline)
        'headline
      (lambda (h)
        (eq (org-element-property :todo-type h)
            'todo))
      nil 'first-match))
  (defun roam-extra:update-todo-tag ()
    "Update TODO tag in the current buffer."
    (defun roam-extra:update-todo-tag ()
      "Update TODO tag in the current buffer."
      (when (and (not (active-minibuffer-window))
                 (org-roam-file-p))
        (org-with-point-at 1
          (let* ((tags (roam-extra:get-filetags))
                 (is-todo (roam-extra:todo-p)))
            (cond ((and is-todo (not (seq-contains-p tags "todo")))
                   (roam-extra:add-filetag "todo"))
                  ((and (not is-todo) (seq-contains-p tags "todo"))
                   (roam-extra:del-filetag "todo"))))))))
  (defun roam-extra:todo-files ()
    "Return a list of roam files containing todo tag."
    (org-roam-db-sync)
    ;; Here I add another call to seq-filter to find nodes with a combination of tags
    (let ((todo-nodes (seq-filter (lambda(n) (seq-contains-p (org-roam-node-tags n)"todo"))
                                  (seq-filter (lambda (n)
                                                (seq-contains-p (org-roam-node-tags n)  "daily"))
                                              (org-roam-node-list)))))
      (seq-uniq (seq-map #'org-roam-node-file todo-nodes))))
  (add-hook! 'find-file-hook #'roam-extra:update-todo-tag)
  (add-hook! 'before-save-hook #'roam-extra:update-todo-tag))

Generate Custom Agenda

  • Set default agenda windows
(after! org-agenda
  (setq org-agenda-start-day "+0d"
        org-agenda-span 7
        org-agenda-breadcrumbs-separator " ❱ "
        org-agenda-block-separator nil))
  • Create a block agenda with the following sections:
    1. Daily overview that includes all scheduled items
    2. A weekly outlook that shows all scheduled items except those we want hidden via a :hide: tag (this relies on helper functions defined below)
    3. Additional sections for org-roam dailies, emails, my β€œmain” todo items, and todo items from org-roam literature notes. This schema relies on having already declared my org-agenda-files and strategically tagging the headlines within.
  • Agenda overdue skip function taken from Trevoke on stackexchage
(after! org-agenda
  (defun timestamp-midnight (timestamp)
  (let ((decoded (decode-time timestamp)))
    (setf (nth 0 decoded) 0)
    (setf (nth 1 decoded) 0)
    (setf (nth 2 decoded) 0)
    (apply #'encode-time decoded)))
  (defun org-agenda-skip-if-scheduled-earlier ()
  "If this function returns nil, the current match should not be skipped.
Otherwise, the function must return a position from where the search
should be continued."
  (ignore-errors
    (let ((subtree-end (save-excursion (org-end-of-subtree t)))
          (scheduled-seconds (org-time-string-to-seconds (org-entry-get nil "SCHEDULED")))
          (now (time-to-seconds (timestamp-midnight (current-time)))))
      (and scheduled-seconds
           (>= scheduled-seconds now)
           subtree-end))))
  (defun org-agenda-skip-if-deadline-later ()
  "Skip entries with deadlines later than now."
  (ignore-errors
    (let ((subtree-end (save-excursion (org-end-of-subtree t)))
          (deadline-seconds (org-time-string-to-seconds (org-entry-get nil "DEADLINE")))
          (now (time-to-seconds (timestamp-midnight (current-time)))))
      (and deadline-seconds
           (>= deadline-seconds now)
           subtree-end))))
  (defun org-agenda-skip-if-scheduled-or-deadline-later ()
  "Skip if either scheduled later or has a deadline later."
  (or (org-agenda-skip-if-scheduled-earlier)
      (org-agenda-skip-if-deadline-later)))

  (defun my/org-agenda-skip-scheduled-or-deadline-later-or-nowarn ()
  "Skip if entry is scheduled or has a deadline later than today or matches the 'nowarn' tag."
  (let ((skip-scheduled-or-deadline (org-agenda-skip-if-scheduled-or-deadline-later))
        (skip-tag (my/org-agenda-skip-without-match "-nowarn")))  ;; Match 'nowarn' tag with `+nowarn`
    (or skip-scheduled-or-deadline skip-tag)))

  (setq org-agenda-custom-commands
        '(("j" "Main agenda and todo list"
           ;; single day agenda sans chores
           ((agenda "" ((org-agenda-span 1)
                        (org-agenda-overriding-header "⚑ Agenda")
                        (org-deadline-past-days 0)
                        (org-scheduled-past-days 0)
                        (org-deadline-warning-days 0)
                        (org-agenda-skip-function
                         '(my/org-agenda-skip-without-match "-chore"))))
            ;; single day agenda only chores
            (agenda "" ((org-agenda-overriding-header "")
                        (org-agenda-time-grid nil)
                        (org-agenda-show-all-dates nil)
                        (org-agenda-format-date "🧹 Chore")
                        (org-agenda-span 1)
                        (org-agenda-entry-types '(:deadline :scheduled))
                        (org-deadline-past-days 0)
                        (org-scheduled-past-days 0)
                        (org-deadline-warning-days 0)
                        (org-agenda-skip-function
                         '(my/org-agenda-skip-without-match "+chore"))))
            ;; all overdue items except "nowarn" chores
            (agenda "" ((org-agenda-overriding-header "")
                        (org-agenda-time-grid nil)
                        (org-agenda-show-all-dates nil)
                        (org-agenda-format-date "⏰ Overdue")
                        (org-agenda-span 1)
                        (org-agenda-entry-types '(:deadline :scheduled))
                        (org-deadline-past-days 999)
                        (org-scheduled-past-days 999)
                        (org-deadline-warning-days 0)
                        (org-agenda-skip-function 'my/org-agenda-skip-scheduled-or-deadline-later-or-nowarn)
                        ;; (org-agenda-skip-function
                        ;;  'org-agenda-skip-if-scheduled-earlier)
                        ))
            ;; all priority todos
            (tags-todo "+PRIORITY=\"A\""  ((org-agenda-overriding-header "βœ… TODO - URGENT βœ…")))
            (tags-todo "+PRIORITY=\"B\""  ((org-agenda-overriding-header "βœ… TODO - IMPORTANT βœ…")))
            (agenda "" ((org-agenda-span 14)
                        (org-agenda-overriding-header "")
                        (org-agenda-start-day "+1d")
                        (org-agenda-skip-function
                         '(my/org-agenda-skip-without-match "-hide"))))
            ;; specific todo views
            (tags-todo "+daily" ((org-agenda-overriding-header "πŸ“… Today")))
            (tags-todo "+email" ((org-agenda-overriding-header "πŸ“§ Email")))
            (tags-todo "+phone" ((org-agenda-overriding-header "πŸ“± Phone")))
            ;; (tags-todo "+main-email" ((org-agenda-overriding-header "βœ… Todo")))
            ;; (tags-todo "+literature" ((org-agenda-overriding-header "πŸ“š Reading")))
            )))))
;; Hide noisy tag labels in agenda
(setq org-agenda-hide-tags-regexp "main\\|chore\\|hide\\|shopping\\|daily\\|calendars\\|email\\|daily\\|attach\\|literature\\|todo\\|phone\\|nowarn")

Agenda Helper Functions

  • Functions relied on by org-agenda-skip-function
  • TODO find original source and cite
(after! org-agenda
  (defun my/org-match-at-point-p (match)
    "Return non-nil if headline at point matches MATCH.
Here MATCH is a match string of the same format used by
`org-tags-view'."
    (funcall (cdr (org-make-tags-matcher match))
             (org-get-todo-state)
             (org-get-tags-at)
             (org-reduced-level (org-current-level))))
  (defun my/org-agenda-skip-without-match (match)
    "Skip current headline unless it matches MATCH.
Return nil if headline containing point matches MATCH (which
should be a match string of the same format used by
`org-tags-view').  If headline does not match, return the
position of the next headline in current buffer.
Intended for use with `org-agenda-skip-function', where this will
skip exactly those headlines that do not match."
    (save-excursion
      (unless (org-at-heading-p) (org-back-to-heading))
      (let ((next-headline (save-excursion
                             (or (outline-next-heading) (point-max)))))
        (if (my/org-match-at-point-p match) nil next-headline)))))
  • Function to launch the custom agenda
(defun org-launch-custom-agenda ()
  "Launch the org agenda using the custom command supplied"
  (interactive)
  (org-agenda nil "j"))

Auto Save Org Buffers

I want to auto save all org buffers every time I load my agenda, so that refreshing the agenda effectively applies any changes I make using the agenda

(add-hook! 'org-agenda-mode-hook #'org-save-all-org-buffers)

Email (mu4e)

Load Path

  • Make sure we can find mu4e
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e") ;; TODO check if this is really needed

Default Behavior

(with-eval-after-load 'mu4e
  (map! :map #'mu4e-view-mode-map
        :g "p" #'my/mu4e-view-save-attachments
        :n "C-=" #'text-scale-increase
        :n "C--" #'text-scale-decrease
        :n "C-_" #'mu4e-headers-split-view-shrink))
(after! mu4e
  ;; (require 'mu4e-contrib) ;; TODO check is this is really needed
  (setq mu4e-main-hide-personal-addresses t
        mu4e-mu-binary "/usr/bin/mu"
        mu4e-get-mail-command "mu index"
        +mu4e-backend 'mbsync
        mu4e-index-update-error-warning nil
        mu4e-index-update-in-background t
        mu4e--update-buffer-height 5
        mu4e-update-interval 60
        mu4e-headers-visible-columns (* (/ (window-total-width) 3) 1)
        mu4e-split-view 'vertical
        mu4e-headers-fields '((:account-stripe . 1)
                              (:human-date . 12)
                              (:flags . 6)
                              (:from-or-to . 25)
                              (:subject . nil))
        mu4e-alert-interesting-mail-query "(maildir:/personal/Inbox OR maildir:/poa/Inbox OR maildir:/gmail/Inbox) AND flag:unread")
  (defun my/mu4e-view-save-attachments (&optional arg)
    "Save MIME-parts from current mu4e gnus view buffer to chosen directory."
    (interactive "P")
    (cl-assert (and (eq major-mode 'mu4e-view-mode)
                    (derived-mode-p 'gnus-article-mode)))
    (let* ((parts (mu4e~view-gather-mime-parts))
           (handles '())
           (files '())
           (compfn (if (and (boundp 'helm-mode) helm-mode)
                       #'completing-read
                     ;; Fallback to `completing-read-multiple' with poor
                     ;; completion
                     #'completing-read-multiple))
           dir)
      (dolist (part parts)
        (let ((fname (or (cdr (assoc 'filename (assoc "attachment" (cdr part))))
                         (cl-loop for item in part
                                  for name = (and (listp item)
                                                  (assoc-default 'name item))
                                  thereis (and (stringp name) name)))))
          (when fname
            (push `(,fname . ,(cdr part)) handles)
            (push fname files))))
      (if files
          (progn
            (setq files (let ((helm-comp-read-use-marked t))
                          (funcall compfn "Save part(s): " files))
                  dir (if arg (read-directory-name "Save to directory: ")
                        (read-directory-name "Save to directory: ")))
            (cl-loop for (f . h) in handles
                     when (member f files)
                     do (mm-save-part-to-file
                         h (let ((file (expand-file-name f dir)))
                             (if (file-exists-p file)
                                 (let (newname (count 1))
                                   (while (and
                                           (setq newname
                                                 (concat
                                                  (file-name-sans-extension file)
                                                  (format "(%s)" count)
                                                  (file-name-extension file t)))
                                           (file-exists-p newname))
                                     (cl-incf count))
                                   newname)
                               file)))))
        (mu4e-message "No attached files found")))))

Do Not Flag Messages Moved to Trash Directory with T flag

][this Github issue]]

(after! mu4e
  (setf (alist-get 'trash mu4e-marks)
      (list :char '("d" . "β–Ό")
            :prompt "dtrash"
            :dyn-target (lambda (target msg)
                          (mu4e-get-trash-folder msg))
            :action (lambda (docid msg target)
                      ;; Here's the main difference to the regular trash mark,
                      ;; no +T before -N so the message is not marked as
                      ;; IMAP-deleted:
                      (mu4e--server-move docid (mu4e--mark-check-target target) "-N")))))

Mark messages as spam and move to mu4e-spam-folder

(after! mu4e
  (add-to-list 'mu4e-marks
               '(spam
                 :char       "s"
                 :prompt     "Spam"
                 :dyn-target (lambda (target msg) (my/mu4e--get-folder 'mu4e-spam-folder msg))
                 :action (lambda (docid msg target)
                           (mu4e--server-move docid (mu4e--mark-check-target target) "+S-u-N"))))
  (mu4e~headers-defun-mark-for spam)
  (defun my/mu4e--get-folder (foldervar msg)
    "Extend mu4e--get-folder to include mu4e-spam-folder"
    (unless (member foldervar
                    '(mu4e-sent-folder mu4e-drafts-folder
                      mu4e-trash-folder
                      mu4e-refile-folder
                      mu4e-spam-folder))
      (mu4e-error "Folder must be one of mu4e-(sent|drafts|trash|refile|spam)-folder"))
    ;; get the value with the vars for the relevants context let-bound
    (with-mu4e-context-vars (mu4e-context-determine msg nil)
        (let* ((folder (symbol-value foldervar))
               (val
                (cond
                 ((stringp   folder) folder)
                 ((functionp folder) (funcall folder msg))
                 (t (mu4e-error "Unsupported type for %S" folder)))))
          (or val (mu4e-error "%S evaluates to nil" foldervar))))))

Composing

  • I don’t know what this didn’t work with a normal setq in and after! block so we added a hook. For some reason no messages I replied to were sending as html which was messing everything up in my mu4e config.
(after! org-msg
  (defun my/org-msg-set-default-alternatives ()
    "Set default alternatives for org msg"
    (setq org-msg-default-alternatives '((new . (utf-8 html))
                                         (reply-to-text . (utf-8 html))
                                         (reply-to-html . (utf-8 html)))))
  (add-hook! 'org-msg-mode-hook #'my/org-msg-set-default-alternatives))
(add-hook 'message-sent-hook (lambda () (setq buffer-undo-list nil)))

Colorization

  • Reduce html coloring in messages for improved readability
(after! mu4e
  (setq mu4e-html2text-command 'mu4e-shr2text
        shr-color-visible-luminance-min 60
        shr-color-visible-distance-min 5
        shr-use-colors nil)
(advice-add #'shr-colorize-region :around (defun shr-no-colourise-region (&rest ignore))))
  • Colorize account stripe per context
(after! mu4e
  (defface mu4e-personal-mail-face '((t (:foreground "#dcdcaa")))
    "Face for personal mail.")
  (defface mu4e-work-mail-face '((t (:foreground "#2257a0")))
    "Face for work mail.")
  (defface mu4e-old-mail-face '((t (:foreground "#c16b6b")))
    "Face for personal mail.")
  (setq
   +mu4e-header--maildir-colors '(("poa" . mu4e-work-mail-face)
                                  ("personal" . mu4e-personal-mail-face)
                                  ("old" . mu4e-old-mail-face))))

Bookmarks

  • Set our custom search queries for mu4e’s homepage
(after! mu4e
  (setq mu4e-bookmarks
        '((:name "πŸ“§ All Mail"
           :query "maildir:/personal/Inbox OR maildir:/poa/Inbox OR maildir:/old/Inbox AND NOT flag:trashed"
           :key 97)
          (:name "⁉ Unread Messages"
           :query "(maildir:/personal/Inbox OR maildir:/poa/Inbox OR maildir:/old/Inbox) AND flag:unread AND NOT flag:trashed"
           :key 117)
          (:name "πŸ”₯ Spam"
           :query "maildir:/personal/\[Gmail\]/Spam OR maildir:/poa/\"Junk Email\" OR maildir:/old/\[Gmail\]/Spam AND NOT flag:trashed"
           :key 115
           )
          (:name "πŸ—‘ Deleted Items"
           :query "maildir:/personal/\[Gmail\]/Trash OR maildir:/poa/\"Deleted Items\" OR maildir:/old/\[Gmail\]/Trash"
           :hide-unread t
           :key 100)
          (:name "🚩 Flagged"
           :query "flag:flagged "
           :hide-unread t
           :key 102)
          (:name "✈ Sent"
           :query "maildir:/personal/\[Gmail\]/\"Sent Mail\" OR maildir:/old/\[Gmail\]/\"Sent Mail\" OR maildir:/poa/\"Sent Items\" AND NOT flag:trashed"
           :hide-unread t
           :key 116))))

Contexts

  • Create a context for each mail account
  • TODO consider offloading this configuration to a separate file
(after! mu4e
  (setq mu4e-contexts
        (list
         ;; personal
         (make-mu4e-context
          :name "personal"
          :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/personal" (mu4e-message-field msg :maildir))))
          :vars '((mu4e-sent-folder       . "/personal/[Gmail]/Sent Mail")
                  (mu4e-drafts-folder     . "/personal/[Gmail]/Drafts")
                  (mu4e-trash-folder      . "/personal/[Gmail]/Trash")
                  (mu4e-spam-folder      . "/personal/[Gmail]/Spam")
                  (mu4e-refile-folder     . "/personal/[Gmail]/All Mail")
                  (smtpmail-smtp-user     . "[email protected]")
                  (smtpmail-smtp-server   . "smtp.gmail.com")
                  (smtpmail-auth-credentials . "~/.authinfo.gpg")
                  (user-mail-address . "[email protected]")
                  (smtpmail-smtp-service   . 587)
                  (smtpmail-stream-type   . starttls)
                  (org-msg-greeting-fmt . "\n-David")
                  (+mu4e-personal-addresses . ("[email protected]"
                                               "[email protected]"
                                               "[email protected]"
                                               "[email protected]"
                                               "[email protected]"))))
         ;; (old) gmail
         ;; renamed to prevent mu4e from knowing it's a gmail account and handing delete commands differently
         (make-mu4e-context
          :name "old"
          :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/old" (mu4e-message-field msg :maildir))))
          :vars '((mu4e-sent-folder       . "/old/[Gmail]/Sent Mail")
                  (mu4e-drafts-folder     . "/old/[Gmail]/Drafts")
                  (mu4e-trash-folder      . "/old/[Gmail]/Trash")
                  (mu4e-refile-folder     . "/old/[Gmail]/All Mail")
                  (mu4e-spam-folder      .  "/old/[Gmail]/Spam")
                  (smtpmail-smtp-user     . "[email protected]")
                  (smtpmail-smtp-server   . "smtp.gmail.com")
                  (smtpmail-auth-credentials . "~/.authinfo.gpg")
                  (user-mail-address . "[email protected]")
                  (smtpmail-smtp-service   . 587)
                  (smtpmail-stream-type   . starttls)
                  (org-msg-greeting-fmt . "\n-David")
                  (+mu4e-personal-addresses . ("[email protected]"
                                               "[email protected]"))))
         ;; work
         (make-mu4e-context
          :name "Plus One"
          :match-func
          (lambda (msg)
            (when msg
              (string-prefix-p "/poa" (mu4e-message-field msg :maildir))))
          :vars `((mu4e-drafts-folder  . "/poa/Drafts")
                  (mu4e-trash-folder      . "/poa/Deleted Items")
                  (mu4e-refile-folder  . "/poa/Inbox")
                  (mu4e-sent-folder  . "/poa/Sent Items")
                  (mu4e-spam-folder  . "/poa/Junk Email")
                  (smtpmail-smtp-user     . "[email protected]")
                  (smtpmail-smtp-server . "smtp.office365.com")
                  (smtpmail-smtp-service . 587)
                  (smtpmail-stream-type . starttls)
                  (user-mail-address . "[email protected]")
                  (org-msg-greeting-fmt . ,(file-to-string "~/Sync/templates/poa-signature.txt"))
                 (+mu4e-personal-addresses . ("[email protected]"))
                  )))))

Dashboard

(defadvice! my/mu4e--main-redraw ()
  "customize the mu4e main menu"
  :override #'mu4e--main-redraw
  (when-let* ((buffer (get-buffer mu4e-main-buffer-name))
              (buffer (and (buffer-live-p buffer) buffer)))
    (with-current-buffer buffer
        (let* ((inhibit-read-only t)
               (pos (point))
               (addrs (mu4e-personal-addresses))
               (max-length (seq-reduce (lambda (a b)
                                         (max a (length (plist-get b :name))))
                                       (mu4e-query-items) 0)))
          (mu4e-main-mode)
          (erase-buffer)
          (insert
           "πŸ“¨ "
           (propertize "mu4e" 'face 'mu4e-header-key-face)
           (propertize " - mu for emacs version " 'face 'mu4e-title-face)
           (propertize  mu4e-mu-version 'face 'mu4e-header-key-face)
           "\n\n"
           (propertize "  Quick Commands\n\n" 'face 'mu4e-title-face)
           (mu4e--main-action
            "\tπŸ‘‰ [@]jump to some maildir\n" #'mu4e-search-maildir nil "J") (mu4e--main-action
            "\tπŸ”Ž enter a [@]search query\n" #'mu4e-search nil "s")
           (mu4e--main-action
            "\tπŸš€ [@]Compose a new message\n" #'mu4e-compose-new nil "C")
           "\n"
           (propertize "  Bookmarks\n\n" 'face 'mu4e-title-face)
           (mu4e--main-items 'bookmarks max-length)
           "\n"
           (propertize "  Misc\n\n" 'face 'mu4e-title-face)
           (mu4e--main-action "\tπŸ”€ [@]Switch context\n"
                              #'mu4e-context-switch nil ";")
           (mu4e--main-action "\tβ™» [@]Update email & database\n"
                                  #'mu4e-update-mail-and-index nil "U")
           ;; show the queue functions if `smtpmail-queue-dir' is defined
           (if (file-directory-p smtpmail-queue-dir)
               (mu4e--main-view-queue)
             "")
           "\n"
           (mu4e--main-action "\tπŸ—ž [@]News\n" #'mu4e-news nil "N")
           (mu4e--main-action "\tπŸ“š [@]About mu4e\n" #'mu4e-about nil "A")
           (mu4e--main-action "\t❓ [@]Help\n" #'mu4e-display-manual nil "H")
           (mu4e--main-action "\t🚫 [@]quit\n" #'mu4e-quit nil "q")
           "\n"
           (propertize "  Info\n\n" 'face 'mu4e-title-face)
           (mu4e--key-val "πŸ•° last updated"
                          (current-time-string
                           (plist-get mu4e-index-update-status :tstamp)))
           (mu4e--key-val "β†ͺ database-path" (mu4e-database-path))
           (mu4e--key-val "πŸ“§ maildir" (mu4e-root-maildir))
           (mu4e--key-val "πŸ’Ύ in store"
                          (format "%d" (plist-get mu4e--server-props :doccount))
                          "messages")
           (if mu4e-main-hide-personal-addresses ""
             (mu4e--key-val "personal addresses"
                            (if addrs (mapconcat #'identity addrs ", "  ) "none"))))
          (if mu4e-main-hide-personal-addresses ""
            (unless (mu4e-personal-address-p user-mail-address)
              (mu4e-message (concat
                             "Tip: `user-mail-address' ('%s') is not part "
                             "of mu's addresses; add it with 'mu init
                        --my-address='") user-mail-address)))
          (goto-char pos))))
  )
(defadvice! my/mu4e--key-val (key val &optional unit)
  "Show a KEY / VAL pair without radios, with optional UNIT."
  :override #'mu4e--key-val
  (concat
   "\t"
   (propertize (format "%-20s" key) 'face 'mu4e-header-title-face)
   ": "
   (propertize val 'face 'mu4e-header-key-face)
   (if unit
       (propertize (concat " " unit) 'face 'mu4e-header-title-face)
     "")
   "\n"))
(defadvice! my/mu4e--main-items (item-type max-length)
"Change the rendering order of items in the mu4e--main-items"
:override #'mu4e--main-items
  (mapconcat
   (lambda (item)
     (cl-destructuring-bind
         (&key hide name key favorite query &allow-other-keys) item
       ;; hide items explicitly hidden, without key or wrong category.
       (if hide
           ""
         (let ((item-info
                ;; note, we have a function for the binding,
                ;; and perhaps a different one for the lambda.
                (cond
                 ((eq item-type 'maildirs)
                  (list #'mu4e-search-maildir #'mu4e-search
                        query))
                 ((eq item-type 'bookmarks)
                  (list #'mu4e-search-bookmark #'mu4e-search-bookmark
                        (mu4e-get-bookmark-query key)))
                 (t
                  (mu4e-error "Invalid item-type %s" item-type)))))
           (concat
            (mu4e--main-action
             ;; main title
             (format "\t %s [@] "
                     (propertize
                      name
                      'face (if favorite 'mu4e-header-key-face nil)
                      'help-echo query))
             ;; function to call when activated
             (lambda () (interactive)
               (funcall (nth 1 item-info)
                        (nth 2 item-info)))
             ;; custom key binding string
             (concat (mu4e-key-description (nth 0 item-info)) (string key)))
            ;; counts
            (format "%s%s\n"
                    (make-string (- max-length (string-width name)) ?\s)
                    (mu4e--query-item-display-counts item)))))))
   ;; only items which have a single-character :key
   (mu4e-filter-single-key (mu4e-query-items item-type)) ""))

Other packages

cfw:calendar

Default Calendar Behavior

(after! cfw:calendar
  ;; Show only desired holidays
  (setq calendar-holidays
        (append holiday-general-holidays
                holiday-hebrew-holidays
                holiday-solar-holidays)))

Create a Custom Calendar

(defun djs-my-personal-calendar ()
  (interactive)
  (cfw:open-calendar-buffer
   :contents-sources
   (list
    ;; for some reason this dummy file help prevents a bug where cfw:org-to-calendar will regester as void in my helper
    (cfw:org-create-file-source "" "~/Sync/projects/org/calendars/dummy.org" (doom-color 'fg))
    (djs-cfw:org-create-file-source "Appts." "~/Sync/projects/org/appt.org" (doom-color 'yellow)(doom-color 'bg))
    (djs-cfw:org-create-file-source "Todo" "~/Sync/projects/org/todo.org" (doom-color 'magenta) (doom-color 'base0))
    (djs-cfw:org-create-file-source "Calendar" "~/Sync/projects/org/calendars/2022-2023.org"   (doom-color 'cyan) (doom-color 'base0))
    (cfw:cal-create-source "#a9a1e1")
    (cfw:ical-create-source "Astro" "~/.doom.d/lunar-phases.ics" "#a9a5aa")
    (cfw:ical-create-source "PGA" "~/.doom.d/pga-tour.ics" (doom-color 'mid-blue)))))
(defun djs-cfw:org-create-file-source (name file color bgcolor)
  "Create org-element based source with cusomg bg-color "
  (lexical-let ((file file))
    (make-cfw:source
     :name (concat "Org:" name)
     :color color
     :period-fgcolor color
     :period-bgcolor bgcolor
     :data (lambda (begin end)
             (cfw:org-to-calendar file begin end)))))

Info-mode

  • Prefer variable pitch for reading in Info-mode and fix an annoying little feature where previous nodes would restore my cursor to the bottom of the page when navigating.
(add-hook! 'Info-mode-hook #'variable-pitch-mode)
(defadvice! djs-Info-goto-top-of-node ()
  "Move cursor to the top of info node"
  :after #'Info-backward-node
  :after #'Info-prev
  :after #'Info-history-back
  (goto-char (point-min)))

command-log-mode

  • Here I just add some defaults to enable command-log-mode for all buffers when activated, and to automatically show the log window whenever I activate the mode
(after! command-log-mode
  (setq command-log-mode-is-global t
        command-log-mode-open-log-turns-on-mode t
        command-log-mode-auto-show t
        command-log-mode-window-font-size 1))

chatgpt

Authnetication and Defaults

(after! gptel
  (setq gptel-api-key (my/lookup-password :host "openai.com")
        gptel-default-mode 'org-mode
        gptel-prompt-prefix-alist  '((markdown-mode . "### ")
                                     (org-mode . "* ")
                                     (text-mode . "### ")))
  (setq-default gptel-model 'gpt-4o))

Autosave buffers

(defun my/save-chatgpt-buffer ()
  (when (and (bound-and-true-p gptel-mode) (not (buffer-file-name)))
    (let* ((timestamp (format-time-string "%Y-%m-%d-T%H-%M-%S"))
           (buffer-name (replace-regexp-in-string "[^a-zA-Z ]" "" (downcase (buffer-name))))
           (clean-buffer-name (replace-regexp-in-string " " "-" buffer-name))
           (filename (format "~/Sync/projects/org/chatgpt/autosave/%s-%s.org" timestamp clean-buffer-name)))
      (write-region (point-min) (point-max) filename)
      (set-visited-file-name filename)
      (set-buffer-modified-p nil))))


(add-hook! 'gptel-mode-hook #'my/save-chatgpt-buffer)

Search GPT files

(defun my/consult-ripgrep-chatgpt ()
  "Use consult-ripgrep to search my chatgpt directory."
  (interactive)
  (let ((default-directory "~/Sync/projects/org/chatgpt"))
    (consult-ripgrep default-directory)))

Autosave conversations

(defun my/turn-on-auto-save-for-gptel-buffers ()
  "Enable auto-save for Gptel buffers only."
  (when (and (bound-and-true-p gptel-mode))
    (auto-save-visited-mode)))

;; add the hook
(add-hook 'gptel-mode-hook 'my/turn-on-auto-save-for-gptel-buffers)

org-modern-indent

(add-hook! 'org-mode-hook #'org-modern-indent-mode)
;; FIXME still have bracket spacing issues
(set-face-attribute 'fixed-pitch nil :family "Berkeley Mono" :height 1.0) ; or whatever font family

mlscroll

(use-package mlscroll
  :ensure t
  :config
  (setq mlscroll-in-color "#96a8ff"
        mlscroll-out-color "#131313")
  (mlscroll-mode 1))

org-caldav

(after! org-caldav
  (setq! org-caldav-url 'google
         org-caldav-calendar-id "[email protected]"
         org-caldav-inbox "~/Sync/projects/org/gcal.org"
         org-caldav-calendars
         ;; gcal
         '((:calendar-id "[email protected]" :files ("~/Sync/projects/org/gcal.org")
            :inbox (file+olp "~/Sync/projects/org/gcal.org" "πŸ“₯ Inbox" ))
           ;; org
           (:calendar-id "c_48c11bffb44b3c632b38ea691b37534fc66531be1e134fcf5a244cc53ad4768f@group.calendar.google.com"
            :files ("~/Sync/projects/org/appt.org")
            :inbox (file+olp "~/Sync/projects/org/gcal.org" "πŸ“₯ Inbox" ))
           ;; travel
           (:calendar-id "c_ee056740f465b21064ecbf1a87b01fd5c5ceee1ebc1088711ada9fbff7b10663@group.calendar.google.com"
            :files ("~/Sync/projects/org/calendars/2022-2023.org")
            :inbox (file+olp "~/Sync/projects/org/gcal.org" "πŸ“₯ Inbox" ) )
           ;; chores
           (:calendar-id "c_7bfefd195373dd9e5d60e7b3014066eda6c8be3764f02f3c57ad3d4eff62a8e4@group.calendar.google.com"
            :files ("~/Sync/projects/org/chore.org")
            :inbox (file+olp "~/Sync/projects/org/gcal.org" "πŸ“₯ Inbox" ) )
           )
         org-caldav-oauth2-client-id (my/lookup-username :host "org-caldav")
         org-caldav-oauth2-client-secret (my/lookup-password :host "org-caldav")
         org-caldav-sync-changes-to-org 'all
         org-caldav-backup-file "~/Sync/projects/org/org-caldav/caldav-backup.org"
         org-caldav-save-directory "~/Sync/projects/org/org-caldav/"
         ;; org-icalendar-include-todo 'all
         ;; org-caldav-sync-todo t
         ))

(after! org
  (setq org-icalendar-timezone "America/New_York"
        org-icalendar-alarm-time 30))
;; org-icalendar-use-deadline 'event-if-todo))

(setq plstore-cache-passphrase-for-symmetric-encryption t)
;; (when init-file-debug
;;   (require 'benchmark-init)
;;   (add-hook 'doom-first-input-hook #'benchmark-init/deactivate))