;;; syd-org.el -*- lexical-binding: t; -*- (with-eval-after-load 'org (syd-add-hook 'org-tab-first-hook (defun syd-org-cycle-only-current-subtree-h (&optional arg) "Toggle the local fold at the point, and no deeper. `org-cycle's standard behavior is to cycle between three levels: collapsed, subtree and whole document. This is slow, especially in larger org buffer. Most of the time I just want to peek into the current subtree -- at most, expand *only* the current subtree. All my (performant) foldings needs are met between this and `org-show-subtree' (on zO for evil users), and `org-cycle' on shift-TAB if I need it." (interactive "P") (unless (or (eq this-command 'org-shifttab) (and (bound-and-true-p org-cdlatex-mode) (or (org-inside-LaTeX-fragment-p) (org-inside-latex-macro-p)))) (save-excursion (org-beginning-of-line) (let (invisible-p) (when (and (org-at-heading-p) (or org-cycle-open-archived-trees (not (member org-archive-tag (org-get-tags)))) (or (not arg) (setq invisible-p (memq (get-char-property (line-end-position) 'invisible) '(outline org-fold-outline))))) (unless invisible-p (setq org-cycle-subtree-status 'subtree)) (org-cycle-internal-local) t))))))) (defun syd-org--init-hacks-h () ;; Open file links in current window, rather than new ones (setf (alist-get 'file org-link-frame-setup) #'find-file) ;; Open directory links in dired (add-to-list 'org-file-apps '(directory . emacs)) (add-to-list 'org-file-apps '(remote . emacs)) (defun syd-org--restart-mode-h () "Restart `org-mode', but only once." (syd-quietly (org-mode-restart)) (setq org-agenda-new-buffers (delq (current-buffer) org-agenda-new-buffers)) (run-hooks 'find-file-hook)) (syd-add-hook 'org-agenda-finalize-hook (defun syd-org-exclude-agenda-buffers-from-workspace-h () "Don't associate temporary agenda buffers with current workspace." (when (and org-agenda-new-buffers (bound-and-true-p persp-mode) (not org-agenda-sticky)) (let (persp-autokill-buffer-on-remove) (persp-remove-buffer org-agenda-new-buffers (get-current-persp) nil))))) (syd-defadvice syd-org--restart-mode-before-indirect-buffer-a (&optional buffer _) "Restart `org-mode' in buffers in which the mode has been deferred (see `syd-org-defer-mode-in-agenda-buffers-h') before they become the base buffer for an indirect org-cpature buffer. This ensures that the buffer is fully functional not only when the *user* visits it, but also when org-capture interacts with it via an indirect buffer." :before #'org-capture-get-indirect-buffer (with-current-buffer (or buffer (current-buffer)) (when (memq #'syd-org--restart-mode-h on-switch-buffer-hook) (syd-org--restart-mode-h)))) (defvar recentf-exclude) (syd-defadvice syd-org--optimise-backgrounded-agenda-buffers-a (fn file) "Disable `org-mode's startup processes for temporary agenda buffers. Prevents recentf pollution as well. However, if the user tries to visit one of these buffers they'll see a gimped, half-broken org buffer, so to avoid that, install a hook to restart `org-mode' when they're switched to so they can grow up to be fully-fledged org-mode buffers." :around #'org-get-agenda-file-buffer (if-let* ((buf (org-find-base-buffer-visiting file))) buf (let ((recentf-exclude '(always)) ;; (doom-inhibit-large-file-detection t) ;; (doom-inhibit-local-var-hooks t) (org-inhibit-startup t) vc-handled-backends enable-local-variables find-file-hook) (when-let ((buf (delay-mode-hooks (funcall fn file)))) (with-current-buffer buf (add-hook 'on-switch-buffer-hook #'syd-org--restart-mode-h nil 'local)) buf)))) (syd-defadvice syd-org--fix-inconsistent-uuidgen-case-a (uuid) "Ensure uuidgen is always lowercase (consistent) regardless of system. See https://lists.gnu.org/archive/html/emacs-orgmode/2019-07/msg00081.html." :filter-return #'org-id-new (if (eq org-id-method 'uuid) (downcase uuid) uuid))) (defun syd-org-init-theme () (require 'syd-kanagawa) (let* ((hl `(:weight bold)) (fg (lambda (c) `(:foreground ,(syd-kanagawa-get c)))) (bg (lambda (c) `(:background ,(syd-kanagawa-get c)))) (block-delim `(:foreground unspecified :inherit font-lock-comment-face :extend t ,@(funcall bg 'sumi-ink-0) :height 0.75)) (keyword '(:background unspecified :foreground unspecified :inherit (fixed-pitch font-lock-comment-face) :height 0.9))) (setq org-src-block-faces `(("jupyter-apl" syd-apl))) (custom-theme-set-faces 'user `(org-document-title ((t (,@hl :height 1.60)))) `(org-document-info ((t (,@hl :height 1.0)))) `(org-document-info-keyword ((t ,keyword))) `(org-meta-line ((t ,keyword))) `(org-level-1 ((t (,@hl :height 1.40 ,@(funcall fg 'oni-violet))))) `(org-level-2 ((t (,@hl :height 1.35 ,@(funcall fg 'crystal-blue))))) `(org-level-3 ((t (,@hl :height 1.30 ,@(funcall fg 'spring-violet-2))))) `(org-level-4 ((t (,@hl :height 1.25 ,@(funcall fg 'light-blue))))) `(org-level-5 ((t (,@hl :height 1.20 ,@(funcall fg 'wave-aqua-2))))) `(org-level-6 ((t (,@hl :height 1.15 ,@(funcall fg 'spring-green))))) `(org-level-7 ((t (,@hl :height 1.10 ,@(funcall fg 'boat-yellow-1))))) `(org-level-8 ((t (,@hl :height 1.05 ,@(funcall fg 'boat-yellow-2))))) `(org-block-begin-line ((t ,block-delim))) `(org-block-end-line ((t ,block-delim))) '(org-ellipsis ((t (:height 1.0)))) ;; It is important that the `org-indent' face uses a fixed-pitch font, lest ;; e.g. multi-line bullets appear misaligned. '(org-indent ((t (:inherit (org-hide syd-alt-fixed-pitch))))) ;; Must be fixed-pitch; `[ ]` and `[X]' should be the same width. '(org-checkbox ((t (:inherit fixed-pitch)))) `(org-drawer ((t ,block-delim))) '(org-property-value ((t (:inherit fixed-pitch)))) '(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch))))) `(org-block ((t (:inherit fixed-pitch ,@(funcall bg 'sumi-ink-2))))) '(org-code ((t (:inherit (shadow fixed-pitch)))))))) (evil-define-command syd-org-yank-link (register) (interactive "") (if-let* ((url (thing-at-point 'url))) (progn (evil-set-register (or register ?\") url) (message "Yanked link: %s" url)) (message "No URL at point"))) (defun syd-org-init-keybinds () (general-def :keymaps 'org-mode-map :states '(insert emacs) [tab] #'org-cycle [C-M-return] #'org-insert-subheading) (general-def :prefix-map 'syd-org-mode-links-map "l" #'org-insert-link "y" #'syd-org-yank-link) (general-define-key :keymaps 'org-mode-map :states '(normal visual motion emacs insert) :major-modes t :prefix syd-localleader-key :non-normal-prefix syd-alt-localleader-key "." #'consult-org-heading "/" #'consult-org-agenda "@" #'org-cite-insert "e" #'org-export-dispatch "f" #'org-footnote-action "h" #'org-toggle-heading "i" #'org-toggle-item "I" #'org-id-get-create "k" #'org-babel-remove-result "l" `("Links" . ,syd-org-mode-links-map) "t" #'org-todo "L" #'org-latex-preview) (general-define-key :keymaps 'org-agenda-mode-map :states '(normal visual motion emacs insert) :major-modes t :prefix syd-localleader-key :non-normal-prefix syd-alt-localleader-key "t" #'org-agenda-todo "r" #'org-agenda-refile "q" #'org-agenda-set-tags "p p" #'org-agenda-priority "p u" #'org-agenda-priority-up "p d" #'org-agenda-priority-down "p k" #'org-agenda-priority-up "p j" #'org-agenda-priority-down "d d" #'org-agenda-deadline "d s" #'org-agenda-schedule)) (defun syd-org-init-popup-rules-h () (set-popup-rules! '(("^\\*Org Links" :slot -1 :vslot -1 :size 2 :ttl 0) ("^ ?\\*\\(?:Agenda Com\\|Calendar\\|Org Export Dispatcher\\)" :slot -1 :vslot -1 :size #'+popup-shrink-to-fit :ttl 0) ("^\\*Org \\(?:Select\\|Attach\\|Table Edit\\)" :slot -1 :vslot -2 :ttl 0 :size 0.25) ("^\\*Edit Formulas\\*$" :slot -1 :vslot -2 :ttl 0 :size 0.25) ("^\\*Org Agenda" :ignore t) ("^\\*Org Src" :size 0.42 :quit nil :select t :autosave t :modeline t :ttl nil) ("^\\*Org-Babel") ("^\\*Capture\\*$\\|CAPTURE-.*$" :size 0.42 :quit nil :select t :autosave ignore)))) (defun syd-org-init-appearance-h () ;; Larger LaTeX previews. (plist-put org-format-latex-options :scale 1.4)) (defun syd-org-init-agenda-h () (setq ;; The lengths of these leaders take account for our added ;; `syd-org--agenda-repeater'. org-agenda-scheduled-leaders '("Sched" "S.%2dx") org-agenda-deadline-leaders '("Deadl" "In%2dd" "D.%2dx") org-agenda-timerange-leaders '("" ; Range within a single day. "%2d/%2d") ; Range spanning many days. ;; Hide "upcoming deadlines" until the scheduled date. org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled ;; Hide completed tasks. org-agenda-skip-scheduled-if-done t) ;; Show the repeater (the repeat interval, e.g. +1d) in repeating agenda ;; entries. (defun syd-org--agenda-repeater () (if (org-before-first-heading-p) "-------" ; fill the time grid (format "%5s: " (or (org-get-repeat) "")))) ;; Add `syd-org--agenda-repeater' to the agenda prefix. (setcdr (assoc 'agenda org-agenda-prefix-format) " %i %-12:c%?-12t%s%(syd-org--agenda-repeater)") (setq org-agenda-custom-commands '(("k" "My agenda for today" ((agenda "" ((org-agenda-span 1) (org-deadline-warning-days 0))) (agenda "" ((org-agenda-span 7) (org-agenda-start-day "+1d") (org-deadline-warning-days 0) (org-agenda-time-grid nil) (org-agenda-entry-types '(:deadline)) (org-agenda-overriding-header "Upcoming deadlines")))))))) (use-package org :defer-incrementally calendar find-func format-spec org-macs org-compat org-faces org-entities org-list org-pcomplete org-src org-footnote org-macro ob org org-agenda org-capture :init ;; HACK: Face specs fed directly to `org-todo-keyword-faces' don't respect ;; underlying faces like the `org-todo' face does, so we define our own ;; intermediary faces that extend from org-todo. (with-no-warnings (custom-declare-face 'syd-org-todo-active '((t (:inherit (bold font-lock-constant-face org-todo)))) "") (custom-declare-face 'syd-org-todo-project '((t (:inherit (bold font-lock-doc-face org-todo)))) "") (custom-declare-face 'syd-org-todo-onhold '((t (:inherit (bold warning org-todo)))) "") (custom-declare-face 'syd-org-todo-cancel '((t (:inherit (bold error org-todo)))) "")) :custom ((org-startup-folded 'content) (org-directory "~/org") ;; Let the agenda be comfortably mutable by storing the list of ;; agenda files in a file. (org-agenda-files "~/org/agenda-files") (org-agenda-deadline-faces '((1.001 . error) (1.0 . org-warning) (0.5 . org-upcoming-deadline) (0.0 . org-upcoming-distant-deadline))) (org-agenda-window-setup 'current-window) (org-refile-use-outline-path t) (org-agenda-skip-unavailable-files t) ;; Shift the agenda to show the previous 3 days and the next 7 days ;; for better context on your week. The past is less important than ;; the future. (org-agenda-span 10) (org-agenda-start-on-weekday nil) (org-agenda-start-day "-3d") ;; Optimize `org-agenda' by inhibiting extra work while opening ;; agenda buffers in the background. They'll be "restarted" if the ;; user switches to them anyway (see ;; `syd-org-exclude-agenda-buffers-from-workspace-h') (org-agenda-inhibit-startup t) ;; Upon finishing a task, leave a timestamp. (org-log-done 'time) (org-indirect-buffer-display 'current-window) ;; Force a TeX-like syntax for {sub,super}-scripts. x^{blah blah} (org-use-sub-superscripts '{}) (org-fontify-quote-and-verse-blocks t) (org-enforce-todo-dependencies t) (org-image-actual-width nil) (org-imenu-depth 6) ;; Don't right-align tags. (org-tags-column 0) (org-priority-faces '((?A . error) (?B . warning) (?C . success))) ;; Stay out of my config dir! (org-id-locations-file (file-name-concat syd-cache-dir "org-id-locations")) ;; New headings should be inserted /after/ the heading's contents. (org-insert-heading-respect-content t) ;; Hide markup syntax and leave the markup. (org-hide-emphasis-markers t) (org-ellipsis " […]") (org-todo-keywords '((sequence "TODO(t)" ; A task that needs doing & is ready to do "PROJ(p)" ; A project, which usually contains other tasks "STRT(s)" ; A task that is in progress "WAIT(w)" ; Something external is holding up this task "HOLD(h)" ; This task is paused/on hold because of me "IDEA(i)" ; An unconfirmed and unapproved task or notion "|" "DONE(d)" ; Task successfully completed "KILL(k)"))) ; Task was cancelled, aborted, or is no longer ; applicable (org-todo-keyword-faces '(("[-]" . syd-org-todo-active) ("STRT" . syd-org-todo-active) ("WAIT" . syd-org-todo-onhold) ("HOLD" . syd-org-todo-onhold) ("PROJ" . syd-org-todo-project) ("KILL" . syd-org-todo-cancel)))) :preface ;; Speed up initialisation by disabling modules we don't need. (defvar org-modules '(;; ol-w3m ;; ol-bbdb ol-bibtex ;; ol-docview ;; ol-gnus ;; ol-info ;; ol-irc ;; ol-mhe ;; ol-rmail ;; ol-eww )) (syd-add-hook 'org-load-hook #'syd-org-init-popup-rules-h #'syd-org-init-appearance-h) (with-eval-after-load 'org-agenda (syd-org-init-agenda-h)) :config (require 'syd-prose) (syd-add-hook 'org-mode-hook #'org-indent-mode #'syd-prose-mode) (syd-org-init-theme) (syd-org-init-keybinds)) (use-package org-appear :hook (org-mode . org-appear-mode) :custom (org-appear-autoemphasis t)) ;; Unlike those appearing in `syd-org--init-roam-keybinds', these should be ;; available even outside of Org-mode. (general-def :prefix-map 'syd-leader-notes-roam-map "f" #'org-roam-node-find) (general-def :keymaps 'syd-leader-open-map "A" #'org-agenda) (defun syd-org--init-roam-keybinds () (general-def :prefix-map 'syd-org-roam-mode-map) (general-def :prefix-map 'syd-leader-notes-map "r" `("Org-roam" . ,syd-leader-notes-roam-map)) (general-define-key :keymaps 'org-mode-map :states '(normal visual motion emacs insert) :major-modes t :prefix syd-localleader-key :non-normal-prefix syd-alt-localleader-key "m" `("Org-roam" . ,syd-org-roam-mode-map))) (use-package org-roam :hook (org-load . syd-org-init-roam-h) :commands (org-roam-buffer-toggle-display org-roam-dailies-find-date org-roam-dailies-find-today org-roam-dailies-find-tomorrow org-roam-dailies-find-yesterday) :init (progn (syd-org--init-roam-keybinds) (syd-load-packages-incrementally '(ansi-color dash f rx seq magit-section emacsql))) :custom ((org-roam-directory org-directory) (org-roam-db-location (file-name-concat syd-data-dir "org-roam.db")) ;; Make org-roam buffer sticky; i.e. don't replace it when opening a ;; file with an *-other-window command. (org-roam-buffer-window-parameters '((no-delete-other-windows . t))) (org-roam-completion-everywhere t)) :config (defun syd-org-init-roam-h () "Setup `org-roam' but don't immediately initialize its database. Instead, initialize it when it will be actually needed." (cl-letf (((symbol-function #'org-roam-db-sync) #'ignore)) (org-roam-db-autosync-enable))) (syd-org--init-roam-keybinds) (syd-defadvice syd-org-roam-try-init-db-a (&rest _) "Try to initialize org-roam database at the last possible safe moment. In case of failure, fail gracefully." :before #'org-roam-db-query (message "Initializing org-roam database...") (advice-remove 'org-roam-db-query #'syd-org-roam-try-init-db-a) (org-roam-db-sync))) (use-package evil-org :hook ((org-mode . evil-org-mode) (org-capture-mode . evil-insert-state)) :straight (:type git :host github :repo "doomelpa/evil-org-mode") :init (defvar evil-org-retain-visual-state-on-shift t) (defvar evil-org-special-o/O '(table-row)) (defvar evil-org-use-additional-insert t) :config (add-hook 'evil-org-mode-hook #'evil-normalize-keymaps) (evil-org-set-key-theme)) (use-package evil-org-agenda :hook (org-agenda-mode . evil-org-agenda-mode) :straight nil :config (evil-org-agenda-set-keys) ;; Stay away from my leader key! (evil-define-key* 'motion evil-org-agenda-mode-map (kbd syd-leader-key) nil)) (use-package hide-mode-line :hook (org-mode . hide-mode-line-mode)) (use-package org-superstar :hook (org-mode . org-superstar-mode) :custom ((org-superstar-headline-bullets-list '(9675)) (org-superstar-item-bullet-alist '((?- . ?•) (?+ . ?➤) (?* . ?⋆)))) :config ;; Stars should use fixed-pitch font to align w/ `org-indent'. (custom-theme-set-faces 'user `(org-superstar-header-bullet ((t (:font ,syd-alt-fixed-pitch-font)))) `(org-superstar-item ((t (:font ,syd-alt-fixed-pitch-font)))))) (use-package org-fragtog :hook (org-mode . org-fragtog-mode)) (provide 'syd-org) ;;; syd-org.el ends here