.emacs.d

the most bloated Emacs config on this planet
git clone https://git.trogloxene.org/.emacs.d.git
Log | Files | Refs

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