diff --git a/users/crumb/programs/emacs/modules/syd-dired.el b/users/crumb/programs/emacs/modules/syd-dired.el new file mode 100644 index 0000000..44de534 --- /dev/null +++ b/users/crumb/programs/emacs/modules/syd-dired.el @@ -0,0 +1,73 @@ +;;; syd-dired.el -*- lexical-binding: t; -*- + +(defun syd-dired-here () + (interactive) + (dired default-directory)) + +(use-package dired + ;; Built-in to Emacs. + :straight nil + :general + (:keymaps 'syd-leader-open-map + "-" #'syd-dired-here) + (:keymaps 'syd-leader-project-map + "-" #'project-dired) + (:keymaps 'dired-mode-map + :states '(normal motion) + "g r" #'revert-buffer) + :commands dired-jump + :custom (;; When there are other Dired windows open, suggest them as targets + ;; for renaming/copying. + (dired-dwim-target t) + ;; Don't prompt to revert, just do it. + (dired-auto-revert-buffer #'dired-buffer-stale-p) + (dired-listing-switches + (mapconcat + #'identity + '("-l" ; Mandatory! + "--almost-all" ; Show hidden files; omit '.' and '..'. + "--human-readable" ; Display sizes in human-readable units. + "--time-style=+") ; Omit times/dates. + " ")) + ;; Always copy recursively + (dired-recursive-copies 'always) + ;; Prompt for confirmation for each top-level directory being + ;; deleted. + (dired-recursive-deletes 'top) + (dired-create-destination-dirs 'ask) + (dired-create-destination-dirs-on-trailing-dirsep t) + ;; Where to store image caches + (image-dired-dir (concat syd-cache-dir "image-dired/")) + (image-dired-db-file (concat image-dired-dir "db.el")) + (image-dired-gallery-dir (concat image-dired-dir "gallery/")) + (image-dired-temp-image-file (concat image-dired-dir "temp-image")) + (image-dired-temp-rotate-image-file (concat image-dired-dir "temp-rotate-image")) + ;; Increase thumbnail sizes. + (image-dired-thumb-size 150)) + :config + (set-popup-rule! "^\\*image-dired" + :slot 20 :size 0.8 :select t :quit nil :ttl 0) + + ;; On ESC, abort `wdired-mode' (will prompt) + (syd-add-hook 'syd-escape-hook + (defun syd-dired--wdired-exit-h () + (when (eq major-mode 'wdired-mode) + (wdired-exit) + t)))) + +(use-package diredfl + :hook ((dired-mode . diredfl-mode) + (dirvish-directory-view-mode . diredfl-mode))) + +(use-package dirvish + :after dired + :custom ((dirvish-cache-dir (file-name-concat syd-cache-dir "dirvish/")) + (dirvish-reuse-session 'open) + (dirvish-default-layout '(0 0.4 0.6)) + (dirvish-hide-details '(dirvish dirvish-fd dirvish-side))) + :config + ;; Fix random void-variable errors. + (require 'autorevert) + (dirvish-override-dired-mode 1)) + +(provide 'syd-dired) diff --git a/users/crumb/programs/emacs/modules/syd-org.el b/users/crumb/programs/emacs/modules/syd-org.el new file mode 100644 index 0000000..2921512 --- /dev/null +++ b/users/crumb/programs/emacs/modules/syd-org.el @@ -0,0 +1,536 @@ +;;; syd-org.el -*- lexical-binding: t; -*- + +;; Optional Org-mode dependency used for highlighting source code in HTML +;; exports. +(use-package htmlize) + +(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 + "s t" #'org-set-tags-command + "s p" #'org-set-property + "s d" #'org-deadline + "s s" #'org-schedule + "d t" #'org-timestamp + "d T" #'org-timestamp-inactive + "x" #'org-toggle-checkbox) + (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 () + "The repeater information (e.g. +1w) in the agenda." + (let ((pom (org-get-at-bol 'org-marker))) + (if (or (org-get-scheduled-time pom) (org-get-deadline-time pom)) + (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")))))))) + +(defvar syd-org-default-css " +" + "A default style for Org HTML exports.") + +(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-tag-persistent-alist `(("orgmode" . ?o) + ("hrt" . ?h))) + (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) + ;; Include some sane default CSS declarations when exporting to HTML. + (org-html-head syd-org-default-css) + ;; 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 + "d t" #'org-roam-dailies-capture-today + "d T" #'org-roam-dailies-goto-today) + +(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)) + ;; Rebind Imenu keybind to `consult-org-heading'. It's similar enough in + ;; appearance and functionality, but more reliable and "correct." + ;; REVIEW: Perhaps it would be best to implement `imenu-create-index-function' + ;; using `consult-org-heading'? + (general-def + :keymaps 'org-mode-map + [remap consult-imenu] #'consult-org-heading + [remap imenu] #'consult-org-heading) + (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-capture-today + org-roam-dailies-goto-date + org-roam-dailies-goto-today + org-roam-dailies-goto-tomorrow + org-roam-dailies-goto-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) + (org-roam-dailies-capture-templates + `(("d" "default" entry "* %?\n%U" + :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>") + :empty-lines 1)))) + :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