;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
(setq user-full-name "DJS"
user-mail-address "[email protected]")
(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")
(modify-all-frames-parameters
'((right-divider-width . 1)
(internal-border-width . 1)))
;; (set-frame-parameter nil 'alpha-background 95)
;; (add-to-list 'default-frame-alist '(alpha-background . 100))
(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)))
(setq rainbow-delimiters-max-face-count 3)
(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))
(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))
(setq delete-by-moving-to-trash t
trash-directory "~/.local/share/Trash/files")
(setq printer-name "hp-neverstop")
- 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))
(+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-mode)
(setq doom-scratch-initial-major-mode 'lisp-interaction-mode)
(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))
(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"))))
(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)))))
;; 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)
(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)))
(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")))
(after! smartparens
(sp-local-pair 'org-mode "~" "~")
(sp-local-pair 'org-mode "=" "="))
(after! calc
(setq calc-algebraic-mode t))
(defun djs-kill-buffer-and-close-window ()
"Kill the current buffer and close the window"
(interactive)
(kill-current-buffer)
(+workspace/close-window-or-workspace))
(defun file-to-string (file)
"File to string function"
(with-temp-buffer
(insert-file-contents file)
(buffer-string)))
(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 "^$")))
- 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 "~/")))
(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"))
(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))))
;; 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)))
(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 changingconsult-find-args
to achieve the desired result instead. Am I even usingfd
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)
))
(after! avy
(setq avy-all-windows 'all-frames))
(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")
;; TODO figure out what's causing some words to highlight even when spelled correctly
(after! flyspell
(setq flyspell-duplicate-distance 0))
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"))))
- 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))
(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}"))))
- 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))))
(use-package treesit-auto
:config
(global-treesit-auto-mode))
- 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!))
(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)
;; (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))
(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)))
(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"))
- 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"))))
(add-hook! 'evil-jumps-post-jump-hook #'+nav-flash-blink-cursor-maybe-h)
- Keep certain windows hanging around longer than Doomβs defaults
(set-popup-rules!
'(("^\\*info\\*" :ignore t)
;; ("^\\*format-all-errors\\*" :select t)
("^\\*Man" :ignore t)))
;; (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)
- 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)))))
(after! persp-mode
(setq persp-emacsclient-init-frame-behaviour-override -1))
;; (setq +format-on-save-enabled-modes
;; (append +format-on-save-enabled-modes '(org-mode)))
- 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)
- Enable rainbow delimiters mode (not sure why this isnβt default)
(add-hook! '(lua-mode-hook
tsx-ts-mode-hook)
#'rainbow-delimiters-mode-enable)
- 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)
- 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))
- Let us mark items as done, prompting for the completion date (credit this Stack Overflow post)
(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)))))))
(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))
(after! org-fancy-priorities
(setq org-fancy-priorities-list '( "π₯" "β" "π€" )))
(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)"))))
(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))))
- 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)))
- 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))
(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/"))
- 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))))
(add-hook! 'org-agenda-mode-hook #'my/disable-word-wrap-mode)
(defun my/disable-word-wrap-mode ()
(+word-wrap-mode -1))
- 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))
- Add any org-roam dailies with open TODOs to the agenda (Souce: Magnus Therningβs blog)
- TODO Ask Magnus for an easier way to sort for multiple tags
(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))
- 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:
- Daily overview that includes all scheduled items
- A weekly outlook that shows all scheduled items except those we want hidden via a
:hide:
tag (this relies on helper functions defined below) - Additional sections for
org-roam
dailies, emails, my βmainβ todo items, and todo items fromorg-roam
literature notes. This schema relies on having already declared myorg-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")
- 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"))
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)
- Make sure we can find mu4e
(add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e") ;; TODO check if this is really needed
(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")))))
][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")))))
- Adapted from Mic92βs Github gist
(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))))))
- I donβt know what this didnβt work with a normal
setq
in andafter!
block so we added a hook. For some reason no messages I replied to were sending as html which was messing everything up in mymu4e
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)))
- 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))))
- 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))))
- 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]"))
)))))
(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)) ""))
(after! cfw:calendar
;; Show only desired holidays
(setq calendar-holidays
(append holiday-general-holidays
holiday-hebrew-holidays
holiday-solar-holidays)))
(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)))))
- 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)))
- 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))
(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))
(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)
(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)))
(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)
(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
(use-package mlscroll
:ensure t
:config
(setq mlscroll-in-color "#96a8ff"
mlscroll-out-color "#131313")
(mlscroll-mode 1))
(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))