init-org.el (21113B)
1 ;;; init-org.el --- org-mode config -*- lexical-binding: t -*- 2 3 ;;; Commentary: 4 5 ;;; Code: 6 7 (my-use-package org 8 :commands org-store-link 9 :hook 10 (org-mode . org-indent-mode) 11 (org-mode . visual-line-mode) 12 (org-mode . electric-pair-local-mode) 13 (org-cycle-tab-first . my-org-tab-complete) 14 15 :init 16 (setq org-directory my-org-directory) 17 18 (defun my-org-tab-complete () 19 (let ((completion-at-point-functions 20 (cond ((or (save-excursion 21 (beginning-of-line) 22 (looking-at-p "[ \t]*#\\+")) 23 (looking-back "\\[\\[\\*" 3)) 24 '(pcomplete-completions-at-point t)) 25 ((org-at-heading-p) 26 nil) 27 ((and (org-in-src-block-p) 28 (equal (car (org-babel-get-src-block-info)) "emacs-lisp")) 29 '(cape-elisp-block t)) 30 (t nil)))) 31 (completion-at-point))) 32 33 :config 34 (defvar org-hide-space-keywords 35 '(("\\cc\\( \\)[*/_=~+]\\cc.*?[*/_=~+]" 36 (0 (prog1 () (add-text-properties (match-beginning 1) (match-end 1) '(invisible t))))) 37 ("[*/_=~+].*?\\cc[*/_=~+]\\( \\)\\cc" 38 (0 (prog1 () (add-text-properties (match-beginning 1) (match-end 1) '(invisible t))))))) 39 (font-lock-add-keywords 'org-mode org-hide-space-keywords 'append) 40 41 (mapc 42 (lambda (kv) 43 (setf (alist-get (car kv) org-src-lang-modes nil nil #'equal) (cdr kv))) 44 '(("yaml" . yaml-ts) 45 ("json" . json-ts) 46 ("toml" . toml-ts))) 47 48 ;; show inline images 49 (setq org-startup-with-inline-images 'inlineimages) 50 51 ;; don't fold headings 52 (setq org-startup-folded 'showall) 53 54 ;; log in drawer 55 (setq org-log-done t 56 org-log-into-drawer t) 57 58 ;; don't set bookmark 59 (setq org-bookmark-names-plist nil) 60 61 ;; todo keywords 62 (setq org-todo-keywords 63 (quote ((sequence "TODO(t)" "SOMEDAY(S)" "STARTED(s)" "WAITING(w)" "|" "DONE(d!/!)" "CANCELLED(c@/!)") 64 (sequence "IDKY(i)" "|" "KNOWN(k)" "CANCELLED(c@/!)") 65 (sequence "GAME(g)" "|" "PLAYED(p)")))) 66 67 ;; agenda 68 (setq org-agenda-span 'day) 69 (setq org-agenda-skip-function-global 70 '(org-agenda-skip-entry-if 'todo '("COMMENT")))) 71 72 ;; optimize line wrapping in tables 73 (my-use-package phscroll 74 :hook 75 (org-mode . org-phscroll-activate) 76 (markdown-mode . phscroll-mode) 77 :init 78 (setq org-startup-truncated nil)) 79 80 ;; consult-denote 81 (my-use-package consult-denote 82 :after denote 83 :demand t 84 :config 85 (consult-denote-mode) 86 (setq consult-denote-buffer-sources nil 87 consult-denote-buffer-source nil 88 consult-denote-subdirectory-source nil) 89 :init 90 (defvar my-denote-exclude-file-glob '("*==archive*" "*==journal*")) 91 92 (defun my-consult-denote-grep (arg) 93 "Call `consult-denote-grep-command' in the variable `denote-directory'." 94 (interactive "P") 95 (let* ((default-directory denote-directory) 96 (excludes (and (not arg) 97 (mapcar (lambda (g) (concat "--exclude=" g)) 98 my-denote-exclude-file-glob))) 99 (consult-grep-args 100 `("grep" (consult--grep-exclude-args) ,@excludes 101 "--null --line-buffered --color=never --ignore-case --with-filename --line-number -I -r"))) 102 (funcall-interactively consult-denote-grep-command))) 103 104 (defun my-consult-denote-journal-grep () 105 "Call `consult-denote-grep-command' in the variable `denote-journal-directory'." 106 (interactive) 107 (let* ((default-directory denote-directory) 108 (includes (format "--include=*==%s*" denote-journal-signature)) 109 (consult-grep-args 110 `("grep" (consult--grep-exclude-args) ,includes 111 "--null --line-buffered --color=never --ignore-case --with-filename --line-number -I -r"))) 112 (funcall-interactively consult-denote-grep-command)))) 113 114 ;; denote 115 (my-use-package denote 116 :hook 117 (find-file . denote-rename-buffer-mode) 118 (dired-mode . denote-dired-mode) 119 (denote-dired-mode . my-denote-dired-exclude-mode) 120 :autoload denote-retrieve-filename-identifier denote-file-prompt 121 :init 122 (defvar denote-directory) 123 (defvar denote-excluded-files-regexp) 124 (defvar denote-journal-signature) 125 (defvar denote-file-prompt-extra-metadata) 126 127 (defun my-denote-find-file (&optional arg) 128 "Find file use denote-open-or-create." 129 (interactive "P") 130 (let ((denote-excluded-files-regexp 131 (unless arg (concat denote-excluded-files-regexp "\\|==" 132 denote-journal-signature 133 "\\|\\(png\\|jpe?g\\|svg\\|gif\\|webp\\|bpm\\|ppm\\|mp[34]\\|mov\\|avi\\|obj\\)$")))) 134 (call-interactively #'denote-open-or-create))) 135 136 (defun my-denote-find-backlink-with-location () 137 "Like `denote-find-backlink-with-location' but with consult preview." 138 (interactive) 139 (when-let* ((current-file (or buffer-file-name 140 (and (derived-mode-p 'dired-mode) 141 (dired-get-filename nil t)))) 142 (id (or (denote-retrieve-filename-identifier current-file) 143 (user-error "The current file does not have a Denote identifier"))) 144 (files (delete current-file (denote-directory-files nil nil :text-only))) 145 (fetcher (lambda () (xref-matches-in-files id files)))) 146 (consult-xref fetcher nil))) 147 148 (defun my-embark-org--refine-link (source) 149 (if (string-prefix-p "denote:" source) 150 (replace-regexp-in-string 151 "::.*" "" (string-remove-prefix "denote:" source)))) 152 153 (defun my-embark-denote-find-backlink-with-location (source) 154 (when-let* ((id (my-embark-org--refine-link source)) 155 (files (delete (denote-get-path-by-id id) (denote-directory-files nil nil :text-only))) 156 (fetcher (lambda () (xref-matches-in-files id files)))) 157 (consult-xref fetcher nil))) 158 159 (defun my-embark-denote-dired-jump (source) 160 (let* ((id (my-embark-org--refine-link source)) 161 (file (denote-get-path-by-id id))) 162 (dired-jump nil file))) 163 164 (defun my-org-dblock-update-all () 165 "Update all dynamic blocks in denote files." 166 (interactive) 167 (let ((recentf-exclude '(".*")) 168 (changed 0)) 169 (dolist (file (denote-directory-files)) 170 (let ((already-open (find-buffer-visiting file)) 171 (buf (find-file-noselect file))) 172 (with-current-buffer buf 173 (with-buffer-unmodified-if-unchanged 174 (when (derived-mode-p 'org-mode) 175 (org-dblock-update t))) 176 (when (buffer-modified-p) 177 (setq changed (1+ changed))) 178 (unless (or (buffer-modified-p) already-open) 179 (kill-current-buffer))))) 180 (message "Updated %s file(s)" changed))) 181 182 (defvar my-denote-link-image-html-width 600) 183 (defun my-denote-link-image-fix (fun file file-type &rest args) 184 "Use custom `denote-org-link-format' if target file is an image." 185 (let ((denote-org-link-format 186 (if (org-file-image-p file) 187 (format "#+attr_html: :width %dpx\n[[denote:%%s]]" 188 my-denote-link-image-html-width) 189 denote-org-link-format))) 190 (apply fun file file-type args))) 191 (advice-add #'denote-link :around #'my-denote-link-image-fix) 192 193 (defun my-denote-dired-exclude--hide () 194 "Determine the lines to be hidden and hide them." 195 (save-excursion 196 (goto-char (point-min)) 197 (let ((marked-files (dired-get-marked-files))) 198 (my-denote-dired-exclude--remove-all-marks) 199 (dolist (file (my-denote-dired-exclude--excluded-files)) 200 (setq file (file-relative-name file)) 201 (setq marked-files (delete (my-denote-dired-exclude--mark-file file) marked-files))) 202 (dired-do-kill-lines nil "") 203 (my-denote-dired-exclude--restore-marks marked-files)))) 204 205 (defun my-denote-dired-exclude--remove-all-marks () 206 (save-excursion 207 (let ((inhibit-read-only t)) 208 (goto-char (point-min)) 209 (while (re-search-forward dired-re-mark nil t) 210 (subst-char-in-region (1- (point)) (point) (preceding-char) ?\s))))) 211 212 (defun my-denote-dired-exclude--restore-marks (marked-files) 213 (dolist (file marked-files) 214 (dired-goto-file file) 215 (dired-mark 1))) 216 217 (defun my-denote-dired-exclude--mark-file (file) 218 (let ((absolute-file (concat (expand-file-name default-directory) file))) 219 (when (and (file-exists-p absolute-file) 220 (dired-goto-file absolute-file)) 221 (dired-mark 1)) 222 absolute-file)) 223 224 (defun my-denote-dired-exclude--excluded-files () 225 (seq-filter 226 (lambda (file) 227 (and (file-regular-p file) 228 (denote-file-has-denoted-filename-p file) 229 (denote--file-excluded-p file) 230 (not (backup-file-name-p file)))) 231 (denote--directory-all-files-recursively))) 232 233 (define-minor-mode my-denote-dired-exclude-mode 234 "Toggle `my-denote-dired-exclude-mode'." 235 :init-value nil 236 :group 'denote-dired 237 (if my-denote-dired-exclude-mode 238 (add-hook 'dired-after-readin-hook #'my-denote-dired-exclude--hide) 239 (remove-hook 'dired-after-readin-hook #'my-denote-dired-exclude--hide)) 240 (revert-buffer)) 241 242 (setq denote-directory my-org-directory 243 denote-org-store-link-to-heading 'context 244 denote-date-prompt-use-org-read-date t 245 denote-prompts '(title signature keywords)) 246 (setq denote-title-history nil 247 denote-signature-history 248 '("note" "project" "fun" "wisdom" "article" "data" "agenda" "journal" "archive") 249 denote-known-keywords 250 '("emacs") 251 denote-excluded-files-regexp "==archive") 252 (with-eval-after-load 'savehist 253 (add-to-list 'savehist-ignored-variables 'denote-title-history))) 254 255 ;; extra tools for org 256 (my-use-package denote-org 257 :autoload denote-org--get-file-id-and-heading-id-or-context 258 :init 259 (defun my-denote-org-find-backlink-for-heading-with-location () 260 "Like `denote-org-backlinks-for-heading' but with consult preview." 261 (interactive) 262 (when-let* ((heading-id (or (denote-org--get-file-id-and-heading-id-or-context) 263 (user-error "The current file does not have a denote identifier"))) 264 (files (denote-directory-files nil :omit-current :text-only)) 265 (fetcher (lambda () (xref-matches-in-files heading-id files)))) 266 (consult-xref fetcher nil)))) 267 268 ;; agenda integration 269 (my-use-package denote-agenda 270 :autoload denote-agenda-insinuate 271 :init 272 (setq denote-agenda-include-regexp "==agenda\\|==project\\|_todo") 273 (with-eval-after-load 'org-agenda 274 (denote-agenda-insinuate)) 275 276 (defun my-denote-agenda-file-p (file) 277 "Non-nil if FILE is a denote-agenda managed file." 278 (and (string-prefix-p (denote-directory) (expand-file-name file)) 279 (string-match-p denote-agenda-include-regexp file))) 280 (add-to-list 'recentf-exclude #'my-denote-agenda-file-p)) 281 282 ;; detect broken links 283 (my-use-package denote-lint 284 :straight 285 (denote-lint :type git 286 :repo "https://git.trogloxene.org/denote-lint.git") 287 :config 288 (setq org-link-elisp-confirm-function nil)) 289 290 ;; denote-journal 291 (my-use-package denote-journal 292 :autoload denote-journal--date-in-interval-p 293 :hook 294 (calendar-mode . denote-journal-calendar-mode) 295 :init 296 (defun my-denote-journal-path-to-existing-entry () 297 "Return the path of current denote journal if it exists, otherwise return nil." 298 (let* ((internal-date 299 (denote-journal--date-in-interval-p (current-time) t)) 300 (files (denote-journal--get-entry internal-date t))) 301 (if files 302 (denote-journal-select-file-prompt files)))) 303 304 (defun my-denote-journal-open-entry () 305 "Open current denote-journal entry if it exists." 306 (interactive) 307 (if (my-denote-journal-path-to-existing-entry) 308 (find-file (my-denote-journal-path-to-existing-entry)) 309 (message "No journal for today yet"))) 310 311 (defun my-denote-journal-find-file () 312 "Find denote journal files." 313 (interactive) 314 (let* ((denote-file-prompt-extra-metadata 315 (list (cons 'category 'denote-file) 316 (cons 'group-function #'denote-file-prompt-group) 317 (cons 'affixation-function #'denote-file-prompt-affixate) 318 (cons 'display-sort-function 319 (lambda (files) (sort files #'string-greaterp))))) 320 (entry (denote-file-prompt "==journal" "Select file" :no-require-match))) 321 (if (file-directory-p entry) 322 (denote-journal-new-or-existing-entry) 323 (find-file entry)))) 324 325 (setq denote-journal-directory (expand-file-name "journal" denote-directory)) 326 (setq denote-journal-keyword nil) 327 (setq denote-journal-signature "journal") 328 (setq denote-journal-keyword "daily") 329 (advice-add 'calendar-redraw :after #'denote-journal-calendar-mark-dates) 330 331 :config 332 (add-to-list 'recentf-exclude (abbreviate-file-name denote-journal-directory))) 333 334 ;; explore, maintain and visualise denote files 335 (my-use-package denote-explore 336 :autoload denote-explore--idenitfy-isolated ;yes, it's a typo from upstream 337 :config 338 (setq denote-explore-isolated-ignore-keywords '("daily")) 339 (require 'ansi-color) 340 (setq chart-face-color-list (list (face-foreground 'ansi-color-blue) 341 (face-foreground 'ansi-color-cyan) 342 (face-foreground 'ansi-color-green) 343 (face-foreground 'ansi-color-yellow) 344 (face-foreground 'ansi-color-red) 345 (face-foreground 'ansi-color-magenta))) 346 347 :init 348 (defun my-denote-explore-isolate-files-dired () 349 "Identify Denote files without (back)links." 350 (interactive) 351 (dired (cons "notes" 352 (mapcar (lambda (file) (car file)) 353 (denote-explore--idenitfy-isolated))))) 354 355 (defun my-denote-explore-network-with-excluded-regexp (fun &rest text-only) 356 (let* ((regexp (read-from-minibuffer "Excluded regexp: ")) 357 (denote-excluded-files-regexp 358 (concat denote-excluded-files-regexp 359 (if (string-empty-p regexp) nil (concat "\\|" regexp))))) 360 (apply fun text-only))) 361 (advice-add #'denote-explore-network :around #'my-denote-explore-network-with-excluded-regexp)) 362 363 ;; easier to insert images 364 (my-use-package org-download 365 :straight 366 (org-download :fork (:type git :host nil :repo "https://git.trogloxene.org/org-download.git")) 367 :after org 368 :demand t 369 :init 370 (defun my-org-download-image-dir-function () 371 (expand-file-name "images/" 372 (project-root (or (project-current nil) 373 '(vc Git "./"))))) 374 375 (setq-default org-download-image-dir #'my-org-download-image-dir-function 376 org-download-heading-lvl nil) 377 (setq org-download-timestamp "%Y%m%dT%H%M%S--" 378 org-download-link-format "[[denote:%s]]\n" 379 org-download-abbreviate-filename-function #'denote-retrieve-filename-identifier 380 org-download-dragndrop t 381 org-download-method 'directory 382 org-image-actual-width nil 383 org-download-image-html-width 600 384 org-return-follows-link t)) 385 386 ;; org-ql 387 (my-use-package org-ql) 388 389 ;; org-super-agenda 390 (my-use-package org-super-agenda 391 :config 392 (org-super-agenda-mode)) 393 394 ;; agenda 395 (my-use-package org-agenda 396 :straight nil 397 :hook 398 (org-agenda-finalize . enable-truncate-lines) 399 :config 400 (setq org-agenda-prefix-format '((agenda . " %?-12t% s") 401 (todo . " ") 402 (tags . " ") 403 (search . " "))) 404 405 (setq org-agenda-sorting-strategy 406 '((agenda time-up scheduled-up deadline-up priority-down category-keep) 407 (todo priority-down category-keep) 408 (tags priority-down category-keep) 409 (search category-keep))) 410 411 (setq org-agenda-custom-commands 412 '(("qd" "TODO entries sort by closed time" 413 ((org-ql-block '(closed) 414 ((org-ql-block-header "Closed TODOs") 415 (org-super-agenda-groups 416 '((:auto-ts t))))))) 417 ("qS" "SOMEDAYs" 418 ((org-ql-block '(todo "SOMEDAY") 419 ((org-ql-block-header "SOMEDAYs"))))) 420 ("qs" "STARTEDs" 421 ((org-ql-block '(todo "STARTED") 422 ((org-ql-block-header "STARTEDs"))))) 423 ("qg" "GAMEs" 424 ((org-ql-block '(todo "GAME") 425 ((org-ql-block-header "GAMEs"))))) 426 ("qp" "PLAYEDs" 427 ((org-ql-block '(todo "PLAYED") 428 ((org-ql-block-header "Closed TODOs") 429 (org-super-agenda-groups 430 '((:auto-ts t))))))) 431 ("qt" "TODOs sort by start time" 432 ((org-ql-block '(todo) 433 ((org-ql-block-header "TODOs") 434 (org-super-agenda-groups 435 '((:auto-ts t))))))) 436 ("qa" "all TODO entries" 437 ((org-ql-block '(and (not (done)) (todo)) 438 ((org-ql-block-header "all TODOs") 439 (org-super-agenda-groups 440 '((:name "started" :todo "STARTED") 441 (:name "waiting" :todo "WAITING") 442 (:name "priority = A" :priority "A") 443 (:name "priority = B" :priority "B") 444 (:name "priority = C" :priority "C") 445 (:name "other priority" :priority< "C") 446 (:name "someday" :todo "SOMEDAY") 447 (:name "I don't know yet" :todo "IDKY") 448 (:name "game" :todo "GAME")))))))))) 449 450 ;; capture 451 (my-use-package org-capture 452 :straight nil 453 :config 454 (setq org-capture-templates 455 `(("t" "TODO" entry 456 (file+headline ,my-org-capture-file "Something to do") 457 "* TODO %<%m-%d> %?\n%T" :empty-lines 1) 458 ("s" "STARTED" entry 459 (file+headline ,my-org-capture-file "Something to do") 460 "* STARTED %<%m-%d> %?\n%T" :empty-lines 1) 461 ("S" "SOMEDAY" entry 462 (file+headline ,my-org-capture-file "Something to do") 463 "* SOMEDAY %<%m-%d> %?\n%T" :empty-lines 1) 464 ("i" "IDKY" entry 465 (file+headline ,my-org-capture-file "Something to know") 466 "* IDKY %<%m-%d> %?\n%T" :empty-lines 1) 467 ("g" "GAME" entry 468 (file+headline ,my-org-capture-game-file "Games") 469 "* GAME %?\n%T" :empty-lines 1)))) 470 471 ;; presentation 472 (my-use-package org-tree-slide my-enable-unessentials 473 :init 474 (defun my-org-tree-slide-start () 475 "Start `org-tree-slide'." 476 (interactive) 477 (when (eq major-mode 'org-mode) 478 (setq-local header-line-format nil 479 mode-line-format nil 480 cursor-type 'hbar 481 olivetti-body-width 100) 482 (blink-cursor-mode -1) 483 (cnfonts-increase-fontsize 5) 484 (hl-line-mode 'toggle) 485 (view-mode 1) 486 (jinx-mode -1) 487 (org-tree-slide-mode 1))) 488 489 (defun my-org-tree-slide-stop () 490 "Stop `org-tree-slide'." 491 (interactive) 492 (when (and (eq major-mode 'org-mode) (memq 'org-tree-slide-mode local-minor-modes)) 493 (org-mode-restart) 494 (blink-cursor-mode 1) 495 (cnfonts-decrease-fontsize 5) 496 (hl-line-mode 1) 497 (view-mode -1) 498 (org-tree-slide-mode -1)))) 499 500 ;; Chinese calendar 501 (my-use-package cal-china-x my-enable-unessentials 502 :after calendar 503 :autoload cal-china-x-setup 504 :init 505 (cal-china-x-setup) 506 :config 507 (setq calendar-mark-holidays-flag t 508 cal-china-x-important-holidays cal-china-x-chinese-holidays 509 cal-china-x-general-holidays '((holiday-lunar 1 15 "元宵节") 510 (holiday-lunar 7 7 "七夕节") 511 (holiday-fixed 3 8 "妇女节") 512 (holiday-fixed 3 12 "植树节") 513 (holiday-fixed 5 4 "青年节") 514 (holiday-fixed 6 1 "儿童节") 515 (holiday-fixed 9 10 "教师节")) 516 holiday-other-holidays '((holiday-fixed 2 14 "情人节") 517 (holiday-fixed 4 1 "愚人节") 518 (holiday-fixed 12 25 "圣诞节") 519 (holiday-fixed 6 7 "高考") 520 (holiday-float 5 0 2 "母亲节") 521 (holiday-float 6 0 3 "父亲节") 522 (holiday-float 11 4 4 "感恩节")) 523 calendar-holidays (append cal-china-x-important-holidays 524 cal-china-x-general-holidays 525 holiday-other-holidays))) 526 527 (provide 'init-org) 528 ;;; init-org.el ends here