(require 'dash) ; lists (require 's) ; strings (require 'f) ; paths & files (defmacro defjoiner (name joiner) `(defun ,name (&rest lines) (s-join ,joiner lines))) (defjoiner spaces " ") (defjoiner lines "\n") (defjoiner commas ", ") (setq user-full-name "Jez Cope" user-mail-address (rot13 "w.pbcr@renzoyre.pb.hx") confirm-kill-emacs nil display-line-numbers-type t fill-column 120 explicit-shell-file-name (executable-find "xonsh") shell-file-name (executable-find "bash")) (setq doom-font (font-spec :family "Iosevka" :size (let ((wd (display-pixel-width))) (cond ((< wd 1600) 12.0) (t 14.0))) :weight 'light) doom-variable-pitch-font (font-spec :family "Iosevka Aile") doom-theme 'doom-gruvbox doom-themes-treemacs-theme "doom-colors") (doom-themes-treemacs-config) (doom-themes-org-config) (setq avy-keys (string-to-list "aoeuhtnsid',.pgcrlyf") lispy-avy-keys avy-keys) (after! ace-window (setq aw-keys (-slice avy-keys 0 20))) (add-hook 'focus-out-hook (lambda () (evil-write-all nil))) (defun clear-popup-rule! (pred) (assoc-delete-all pred +popup--display-buffer-alist)) (defun clear-popup-rules! (&rest preds) (dolist (pred preds) (clear-popup-rule! pred))) (plist-put! +popup-defaults :size 0.38 ;; golden ratio! :side 'right) (clear-popup-rules! "^\\*info\\*$") (set-popup-rules! '(("^\\*\\([Hh]elp\\|Apropos\\)" :side right :slot 2 :vslot -8 :size 0.38 :select t) ("^\\*info\\*$" :side right :size 0.38 :slot 2 :vslot 1) ("^\\*\\(?:doom\\)?:scratch" :side right size 0.38) ("^\\*eww\\*" :actions (+popup-display-buffer-stacked-side-window-fn) :side right :size 0.62 :slot 1 :vslot 1) ("^\\*Capture\\*$\\|CAPTURE-.*$" :side left :size 0.38 :quit nil :select t :autosave ignore) ("^\\*Org Src" ;; :actions (display-buffer-use-some-window) :side right :size 80 :quit nil :select t :autosave t :modeline t :ttl nil))) (use-package org-crypt :after (org) :config (org-crypt-use-before-save-magic) :init (setq org-tags-exclude-from-inheritance '("crypt") org-crypt-key "D9DA 3E47 E8BD 377D A317 B3D0 9E42 CE07 1C45 59D1" auto-save-default nil)) (setq ispell-dictionary "en_GB") (setq +format-on-save-enabled-modes '(python-mode nix-mode)) (nconc +doom-dashboard-menu-sections '(("RSS feeds" :icon (all-the-icons-octicon "rss" :face 'doom-dashboard-menu-title) :when (fboundp 'elfeed) :action elfeed))) (defun doom-dashboard-widget-simple-title () (insert "\n\n" (+doom-dashboard--center +doom-dashboard--width "D O O M E M A C S") "\n")) (when (< (display-pixel-height) 1080) (setq +doom-dashboard-functions (cons 'doom-dashboard-widget-simple-title (delq 'doom-dashboard-widget-banner +doom-dashboard-functions)))) (setq doom-scratch-initial-major-mode 'org-mode) (map! :n "g C-l" #'link-hint-copy-link :n "g M-l" #'link-hint-open-link) (use-package! projectile :config (add-to-list 'projectile-globally-ignored-directories ".sync")) (setenv "SSH_AUTH_SOCK" (f-join "/run/user" (prin1-to-string (user-uid)) "gnupg/S.gpg-agent.ssh")) (setq monky-process-type 'cmdserver) (after! eww (require 'ol-eww)) (add-hook! org-mode #'org-indent-mode #'+org-pretty-mode) (setq org-superstar-headline-bullets-list '("◉" "○" "●" "⋄" "►") org-superstar-item-bullet-alist '((?- . ?•) (?+ . ?⬝) (?* . ?★))) (map! :map org-mode-map :localleader :n "sp" #'org-paste-subtree) (general-add-hook 'org-insert-heading-hook 'evil-insert-state t) ;; append instead of prepending (after! org (setq jc/notes-dir (f-expand "~/Notes") jc/logbook-file (f-expand "logbook.org" jc/notes-dir) org-directory jc/notes-dir org-roam-directory (f-expand "Roam/" jc/notes-dir) org-roam-dailies-directory "daily/" org-id-locations-file (f-expand ".orgids" org-directory) jc/agenda-file-home (f-expand "Todo/todo-home.org" jc/notes-dir) jc/agenda-file-work (f-expand "Todo/todo-work.org" jc/notes-dir) jc/agenda-files-all (list jc/agenda-file-work jc/agenda-file-home) org-agenda-files jc/agenda-files-all jc/spark-file (f-expand "spark.org" org-roam-directory) jc/agenda-file-sets (cons jc/agenda-files-all (mapcar 'list jc/agenda-files-all)) org-archive-location "%s_archive::datetree/" jc/agenda-file-home-archive (concat jc/agenda-file-home "_archive") jc/agenda-file-work-archive (concat jc/agenda-file-work "_archive"))) (add-to-list 'auto-mode-alist '("\\.org_archive\\'" . org-mode)) (defvar jc/agenda-file-sets '() "List of agenda file sets") (defun list-successor (x xs) (if (null xs) nil (let ((y (car xs)) (ys (cdr xs))) (or (catch 'result (while ys (if (equal x y) (throw 'result (car ys)) (setq y (car ys) ys (cdr ys))))) (car xs))))) (defun jc/agenda-file-set-next () (interactive) (let ((next-set (list-successor org-agenda-files jc/agenda-file-sets))) (setq org-agenda-files next-set)) (message (format "Current agenda files: %s" org-agenda-files))) (defun jc/agenda-file-set-choose () (interactive) (let ((sets (--map (cons (s-join "+" it) it) jc/agenda-file-sets))) (ivy-read "Set:" sets :action (lambda (result) (setq org-agenda-files (cdr result)))))) (after! org (setq org-refile-targets `((,jc/agenda-files-all . (:maxlevel . 3)) (,jc/spark-file . (:maxlevel . 2)) (nil . (:maxlevel . 3))) org-refile-use-outline-path t)) (use-package! org-protocol) (add-hook 'org-capture-mode-hook 'evil-insert-state t) (defun jc/org-id-gen (&optional node) (org-id-new)) (setq org-capture-templates `(("t" "Task" entry (file "/home/jez/Notes/Todo/inbox.org") "* TODO %?") ("n" "Note" entry (file "/home/jez/Notes/Todo/inbox.org") "* %?") ("l" "Link" entry (file "/home/jez/Notes/Todo/inbox.org") "* TOREAD %? %:annotation") ("L" "Link task" entry (file "/home/jez/Notes/Todo/inbox.org") "* TODO %? %:annotation")) org-protocol-default-template-key "l" org-roam-capture-templates `(("d" "default" plain ,(lines "categories: %?" "related: ") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n") :empty-lines 1 :unnarrowed t) ("w" "work" plain "%?" :if-new (file+head "work/%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n") :empty-lines 1 :unnarrowed t) ("b" "blog" entry ,(lines "* ${title}" "" "%?") :if-new (file+olp "writing/blog.org" ("Ideas")) :empty-lines 1 :narrowed t)) org-roam-capture-ref-templates '(("r" "ref" plain "%?" :if-new (file+head "${slug}.org" "#+title: ${title}") :unnarrowed t)) org-roam-dailies-capture-templates '(("d" "default" entry "* %?" :if-new (file+head "%<%Y/%m/%Y-%m-%d>.org" "#+title: Daily notes: %<%a %d %b %Y>\n\n")) ("c" "clocking" entry nil :if-new (file+datetree "clocking.org" week) :clock-in)) orb-templates '(("r" "ref" plain #'org-roam-capture--get-point "" :file-name "bibliography/${citekey}" :head "#+title: ${title}\n#+roam_key: ${ref}\n" :unnarrowed t))) (setq org-todo-keywords '((sequence "TODO(t)" "COOL(c)" "WARM(w)" "HOT(h)" "DOING(o!)" "|" "DONE(d!)" "DROPPED(x@)") (sequence "WAITING(a@)" "DELEGATED(l@)" "DISCUSS(s)" "MONITOR(m)" "ONHOLD(H)" "|") (sequence "TOREAD" "READING" "|" "READ")) org-enforce-todo-dependencies t) (setq org-tags-exclude-from-inheritance '("track" "project") org-id-track-globally t org-id-link-to-org-use-id t) (setq org-src-preserve-indentation nil) (use-package! ob-http :after org-babel :commands ob-http-mode) (setq org-roam-db-update-method 'immediate org-roam-tag-sources '(prop first-directory) org-roam-graph-exclude-matcher "daily/" org-roam-graph-extra-config '(("rankdir" . "LR") ("concentrate" . "true")) org-roam-graph-edge-extra-config '(("dir" . "none")) org-roam-node-display-template "${doom-hierarchy:*} ${doom-subdirs:20} ${tags:20}" +org-roam-open-buffer-on-find-file nil) (unless (f-exists? org-roam-directory) (f-mkdir org-roam-directory)) (defun jc/search-org-roam () "search org-roam with ripgrep" (interactive) (counsel-rg nil org-roam-directory "" "org-roam search: ")) (map! :leader :desc "org-roam search" :n "sR" #'jc/search-org-roam) (map! :localleader :mode org-mode :n "mx" #'org-roam-extract-subtree) (require 'org-roam-protocol) (use-package! citar :when (featurep! :completion vertico) :after embark :defer t :config (add-to-list 'embark-keymap-alist '(bib-reference . citar-map))) (setq bibtex-autokey-names 1 bibtex-autokey-additional-names ".ea" bibtex-autokey-name-separator "." bibtex-autokey-name-case-convert 'identity bibtex-autokey-name-year-separator "_" bibtex-autokey-year-length 4 bibtex-autokey-year-title-separator "_" bibtex-autokey-titleword-length 10 bibtex-autokey-titlewords 1 bibtex-autokey-titlewords-stretch 1 bibtex-autokey-titleword-case-convert 'capitalize bibtex-entry-format '(opts-or-alts required-fields numerical-fields whitespace sort-fields) bibtex-completion-notes-path (f-expand "bibliography" org-roam-directory) bibtex-completion-bibliography (f-expand "bibliography.bib" bibtex-completion-notes-path) bibtex-completion-library-path (f-expand "files" bibtex-completion-notes-path) org-ref-default-bibliography (list bibtex-completion-bibliography) org-ref-pdf-directory bibtex-completion-library-path) (use-package! bibtex :defer :config (bibtex-set-dialect 'BibTeX)) (use-package! org-ref-ivy :after (org-ref)) (defvar bibtex-rmapi-destination "/Reading" "The folder on tablet to send PDFs to (starts with a '/')") (defun bibtex-completion-rmapi-function (file) (message "Sending %s to %s on reMarkable" file bibtex-rmapi-destination) (start-process "rmapi" "*rmapi*" "rmapi" "put" file bibtex-rmapi-destination)) (defun bibtex-completion-send-to-rmapi (keys &optional fallback-action) "Send the PDFs associated with the marked entries to reMarkable tablet If multiple PDFs are found for an entry, ask for the one to open using `completion-read'." (let ((bibtex-completion-pdf-open-function 'bibtex-completion-rmapi-function)) (bibtex-completion-open-pdf keys))) (with-eval-after-load 'ivy-bibtex (ivy-bibtex-ivify-action bibtex-completion-send-to-rmapi ivy-bibtex-send-to-rmapi) (ivy-add-actions 'ivy-bibtex '(("R" ivy-bibtex-send-to-rmapi "Send to reMarkable tablet")))) (defun jc/agenda-line-blocked-p (item) (get-text-property 0 'org-todo-blocked item)) (setq org-agenda-dim-blocked-tasks t) (use-package! org-super-agenda :after (org-agenda) :config (setq org-super-agenda-groups '((:name "Future" :and (:todo t :scheduled future) :order 6) (:name "Active projects" :and (:todo t :children ("TODO" "COOL" "WARM" "HOT" "DOING")) :order 15) (:name "Waiting projects" :and (:todo t :children ("WAITING" "DELEGATED")) :order 14) (:name "On hold" :and (:children nil :todo "ONHOLD") :order 4) (:name "On-hold projcts" :and (:todo t :children "ONHOLD") :and (:tag "project" :todo "ONHOLD") :order 14) (:name "Stuck projects" :tag "project" :and (:todo t :children t) :order 13) (:name "Overdue" :and (:todo t :deadline past) :face (:foreground "red")) (:name "Due today" :and (:todo t :deadline today) :face (:foreground "orange")) (:name "Due tomorrow" :and (:todo t :deadline +2)) (:name "Blocked" :and (:todo t :pred jc/agenda-line-blocked-p) :order 8) (:name "Doing (3)" :todo "DOING") (:name "Hot (3)" :todo "HOT") (:name "Warm (5)" :todo "WARM") (:name "Cool (8)" :todo "COOL") (:name "Backlog" :todo "TODO" :order 1) (:name "Waiting" :todo "WAITING" :order 2) (:name "Delegated" :todo "DELEGATED" :order 4))) (org-super-agenda-mode 1)) (setq org-ql-views (let ((kanban-query '(and (todo "DOING" "HOT" "WARM" "COOL" "TODO" "WAITING") (not (or (scheduled :from tomorrow) (property "Project" "t") (children)))))) `(("Working: Contexts" :title "Tasks by context" :buffers-files org-agenda-files :query (and (not (or (done) (todo "ONHOLD" "WAITING" "DELEGATED")) ) (not (scheduled :from tomorrow)) (tags "email" "phone" "online")) :sort todo :super-groups ((:auto-tags t))) ("Kanban: work" :title "Kanban: work" :buffers-files (,jc/agenda-file-work) :query ,kanban-query :super-groups org-super-agenda-groups) ("Kanban: home" :title "Kanban: home" :buffers-files (,jc/agenda-file-home) :query ,kanban-query :super-groups org-super-agenda-groups) ("Focus: work" :title "Focus" :buffers-files (,jc/agenda-file-work) :query (and (todo "DOING" "HOT") (not (or (scheduled :from tomorrow) (property "Project" "t") (children)))) :super-groups org-super-agenda-groups) ("Working: People agendas" :title "Agendas" :buffers-files (,jc/agenda-file-work) :query (and (todo "DELEGATED" "WAITING" "DISCUSS") (tags "BlaM" "BluM" "RK")) :super-groups ((:auto-tags t))) ("Process: Inbox" :buffers-files ,(f-expand "Todo/inbox.org" jc/notes-dir)) ("Review: Recently closed" :title "Closed in the last 2 weeks" :buffers-files ,(list jc/agenda-file-work jc/agenda-file-work-archive) :query (closed :from -14) :super-groups ((:auto-ts t))) ("Review: Projects" :buffers-files org-agenda-files :query (and (todo) (or (tags "project") (children))) :super-groups ((:name "Progressing" :order 15 :children ("TODO" "COOL" "WARM" "HOT" "DOING")) (:name "Discussion needed" :order 4 :children ("DISCUSS")) (:name "Waiting" :order 10 :children ("WAITING" "DELEGATED")) (:name "On hold" :order 10 :children ("ONHOLD")) (:name "Stalled" :order 0 :anything t))) ("Review: Strategic areas" :buffers-files org-agenda-files :query (property "Track") :super-groups ((:name "Progressing" :order 5 :children ("TODO" "COOL" "WARM" "HOT" "DOING")) (:name "Discussion needed" :order 4 :children ("DISCUSS")) (:name "Waiting" :order 10 :children ("WAITING" "DELEGATED")) (:name "On hold" :order 10 :children ("ONHOLD")) (:name "Stalled" :order 0 :anything t)))))) (map! :leader :desc "Org-ql views" :n "oq" #'org-ql-view) (defhydra jc/agenda-views () "agendas" ("f" jc/agenda-file-set-choose "choose agenda file set") ("ht" (org-ql-search (list jc/agenda-file-home) '(todo) :super-groups org-super-agenda-groups) "home tasks") ("hk" (org-ql-view "Kanban: home") "home kanban") ("wt" (org-ql-search (list jc/agenda-file-work) '(todo) :super-groups org-super-agenda-groups) "work tasks") ("wk" (org-ql-view "Kanban: work") "work kanban")) (map! :leader :n "oa" #'jc/agenda-views/body) (setq org-duration-format 'h:mm) (defun jc/org-clock-choose-time (&optional select) (interactive "P") (let ((at-time (encode-time (parse-time-string (org-read-date t)))) (org-clock-continuously t)) (org-clock-out nil nil at-time) (org-clock-in select at-time))) (use-package! deft :custom (deft-extensions '("md" "org" "txt")) (deft-default-extension "md") (deft-directory "~/Scratch/ZettelTest") (deft-use-filename-as-title t)) (use-package! zetteldeft :after deft :config (setq zetteldeft-link-indicator "[[" zetteldeft-link-suffix "]]" zetteldeft-title-prefix "# " zetteldeft-list-prefix "- ") (font-lock-add-keywords 'markdown-mode `((,zetteldeft-id-regex . font-lock-warning-face)))) (setq browse-url-browser-function #'eww-browse-url browse-url-generic-program "xdg-open") (map! :leader :desc "Web search" :n "sw" #'eww-search-words) (after! elfeed (setq elfeed-search-filter "@6-month-ago +unread") (clear-popup-rules! "^\\*elfeed-entry") (set-popup-rule! "^\\*elfeed-entry" :actions '(+popup-display-buffer-stacked-side-window-fn) :side 'right :size 0.62 :slot 1 :vslot 1)) (setq elfeed-db-directory (f-join jc/notes-dir "Data" "elfeed") rmh-elfeed-org-files (list (f-expand "feeds.org" org-roam-directory))) (map! :map elfeed-show-mode-map :n "gy" #'link-hint-copy-link :n "gl" #'link-hint-open-link) (use-package! elpher :commands (elpher) :config (setq elpher-ipv4-always t)) (setq gnutls-verify-error nil) (defun jc/fix-python-date (str) (replace-regexp-in-string "\\([+-][0-9][0-9]\\)\\([0-9][0-9]\\)" "\\1:\\2" str)) (defun jc/update-post-date () (interactive) (save-mark-and-excursion (goto-char (point-min)) (re-search-forward "^date:") (move-beginning-of-line 1) (kill-line) (insert (jc/fix-python-date (format-time-string "date: %FT%T%z"))))) (map! :leader :desc "Run shell command" "!" #'shell-command) (set-email-account! "main" `((user-full-name . "Jez Cope") (user-mail-address . "j.cope@erambler.co.uk") (mu4e-compose-signature . "---\nJez Cope") (mu4e-drafts-folder . "/main/Drafts") (mu4e-trash-folder . "/main/Trash") (mu4e-sent-folder . "/main/Sent") (mu4e-refile-folder . jc/make-refile-folder)) t) (defun jc/make-refile-folder (msg) (format-time-string "/main/Archives.%Y" (mu4e-msg-field msg :date))) (setq mu4e-split-view 'vertical mu4e-headers-visible-columns 80 mu4e-headers-sort-direction 'ascending mu4e-headers-sort-field ':date mu4e-headers-include-related nil mu4e-bookmarks '((:name "Unified Inbox" :query "m:\"/main/Inbox\" OR m:\"/main/Triage.*\"" :key ?I) (:name "Inbox" :query "m:\"/main/Inbox\"" :key ?i) (:name "Unread messages" :query "flag:unread AND NOT flag:trashed AND NOT m:/main/Junk" :key ?u) (:name "Today's messages" :query "date:today..now AND NOT m:/main/Junk" :key ?t) (:name "Last 7 days" :query "date:7d..now AND NOT m:/main/Junk" :hide-unread t :key ?w) (:name "Flagged messages" :query "flag:flagged AND NOT m:/main/Junk" :key ?f)) mu4e-headers-actions '(("capture message" . mu4e-action-capture-message) ("show this thread" . mu4e-action-show-thread))) (after! (mu4e-mark mu4e-headers evil-collection-mu4e) (setq mu4e-junk-folder "/main/Junk" mu4e-marks (append '((archive :char ("a" . "▶") :prompt "archive" :dyn-target (lambda (target msg) (mu4e-get-refile-folder msg)) :action (lambda (docid msg target) (mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N"))) (junk :char ("J" . "🗑") :prompt "Junk" :show-target (lambda (target) "junk") :action (lambda (docid msg target) (mu4e~proc-move docid (mu4e~mark-check-target mu4e-junk-folder) "+S-u-N")))) mu4e-marks)) (mu4e~headers-defun-mark-for junk) (mu4e~headers-defun-mark-for archive) (map! :mode mu4e-headers-mode :n "zJ" #'mu4e-headers-mark-for-junk :n "r" #'mu4e-headers-mark-for-archive)) (after! mu4e (setq sendmail-program (executable-find "msmtp") send-mail-function #'smtpmail-send-it message-sendmail-f-is-evil t message-sendmail-extra-arguments '("--read-envelope-from") message-send-mail-function #'message-send-mail-with-sendmail mu4e-compose--org-msg-toggle-next nil)) (setq-default flycheck-disabled-checkers '()) (add-to-list 'auto-mode-alist '("\\.tmpl\\'" . web-mode)) (setq web-mode-engines-alist '(("go" . "/layouts/.*\\.html\\'")) web-mode-enable-engine-detection t) (add-to-list 'auto-mode-alist '("Pipfile\\'" . toml-mode)) (use-package! prism :custom (prism-parens t) (prism-lightens '(0)) (prism-desaturations '(0))) (after! elisp-mode (remove-hook! emacs-lisp-mode #'rainbow-delimiters-mode)) (after! tex (remove-hook! TeX-update-style #'rainbow-delimiters-mode)) (use-package! aggressive-indent :hook (emacs-lisp-mode . aggressive-indent-mode)) (cl-pushnew 'emacs-lisp-checkdoc flycheck-disabled-checkers) (use-package! symex :custom (symex-modal-backend 'evil) (symex-highlight-p t) :init (setq symex--user-evil-keyspec '(("j" . symex-go-up) ("k" . symex-go-down) ("C-j" . symex-climb-branch) ("C-k" . symex-descend-branch) ("M-j" . symex-goto-highest) ("M-k" . symex-goto-lowest))) :config (symex-initialize) :bind (:map evil-normal-state-map ("s-;" . symex-mode-interface))) (when (equal (system-name) "WXLT203936") (setq url-proxy-services '(("no_proxy" . "bl\\.uk") ("http" . "public-cache.bl.uk:3128") ("https" . "public-cache.bl.uk:3128")) spell-checking-enable-by-default nil jc/notes-dir "f:/Notes"))