λ +org - org mode setup

Author: lispcat 187922791+lispcat@users.noreply.github.com

Configuration for Org-mode and friends.

Org is a markup language and plain text file format, much like Markdown, but a lot more powerful.

;; NOTE: ensure that the newest version of org is installed right after elpaca
;; setup

% org

General config options for org-mode.

(leaf org :elpaca nil
  :custom
  ;; default org directory
  (org-directory . "~/Notes/org")

  ;; column where tags are indented to
  (org-tags-column . -55)

  ;; default folding mode
  (org-startup-folded . 'nofold)

  ;; indent headings and its body
  (org-startup-indented . t)

  ;; more ergonomic C-a/C-e
  (org-special-ctrl-a/e . t)

  ;; edit src blocks in the same window
  (org-src-window-setup . 'current-window)

  ;; RET can open links
  (org-return-follows-link . t)

  ;; hide formatting chars (* / ~ = etc)
  (org-hide-emphasis-markers . t)

  ;; remove annoying leading whitespace in code blocks
  (org-src-preserve-indentation . t)

  ;; TODO: not sure what this does
  ;; (org-fontify-whole-heading-line . t)

  ;; custom ellipses when folded
  ;; (org-ellipsis . " ‣")
  (org-ellipsis . " ›")
  ;; (org-ellipsis . " …")
  ;; (org-ellipsis . " ⤵")
  ;; (org-ellipsis . " ▾")

  :init
  (leader-bind
    "o" '(:ignore t :wk "org"))

  :config

  ;; set org font sizes
  (dolist
      ;; (pair '((org-document-title :height 1.9 :weight bold)
      ;;         (org-level-1 :height 1.7 :weight bold)
      ;;         (org-level-2 :height 1.4 :weight bold)
      ;;         (org-level-2 :height 1.1)
      ;;         (org-level-3 :height 1.1)))
      (pair '((org-document-title :height 1.9)))
    (apply #'set-face-attribute (car pair) nil (cdr pair)))

  ;; fix syntax <> matching with paren
  (add-hook 'org-mode-hook (lambda ()
                             (modify-syntax-entry ?< ".")
                             (modify-syntax-entry ?> ".")))


  ;; keywords override

  (defun +org-todo-color-override (&rest _)
    "Set org-todo-keyword-faces only if not already set by the theme."
    (setq org-todo-keyword-faces
          `(("NEXT" :foreground ,(or (ignore-error
                                         (face-attribute 'highlight :foreground nil 'default))
                                     "yellow")))))

  ;; Advise the load-theme function to run our color override
  (advice-add 'load-theme :after #'+org-todo-color-override)

  ;; Run once immediately to set colors if no theme is loaded
  (+org-todo-color-override)

  ;; Shortcut for M-RET M-<right>
  ;; In org-mode, this usually translates to either:
  ;; - new subheading
  ;; - new sublist
  (defun +org-meta-ret-meta-right ()
    "Shortcut for M-RET M-<right>."
    (interactive)
    (org-meta-return)
    (org-metaright))

  :bind
  (org-mode-map
   ("C-M-<return>" . +org-meta-ret-meta-right)))

> org-tempo

Ease the creation of src blocks (code blocks).

To create src blocks, usually, you would press C-c C-, or run org-insert-structure-template.

But with org-tempo, with the config below, you can type <sh TAB to create an src block like this:

#+begin_src shell

#+end_src
(leaf org-tempo :elpaca nil
  :after org
  :config
  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("py" . "src python"))
  (add-to-list 'org-structure-template-alist '("gcc" . "src c"))
  (add-to-list 'org-structure-template-alist '("scm" . "src scheme"))
  (add-to-list 'org-structure-template-alist '("conf" . "src conf"))
  (add-to-list 'org-structure-template-alist '("java" . "src java"))
  (add-to-list 'org-structure-template-alist '("unix" . "src conf-unix"))
  (add-to-list 'org-structure-template-alist '("clang" . "src c")))

> org-download

(leaf org-download
  :after org
  :config
  (org-download-enable)
  :setq-default
  (org-download-image-dir . "_images"))

> org-bullets

;; TODO: replace with org-superstar
(leaf org-bullets
  :hook org-mode-hook
  :setq
  (org-bullets-bullet-list
   . '("◉"
       "●"
       "○"
       "■"
       "□"
       "✦"
       "✧"
       "✿")))

> toc-org

(leaf toc-org
  :hook org-mode-hook)

> anki-editor

(leaf anki-editor
  :commands (anki-editor-push-note-at-point
             anki-editor-push-notes
             anki-editor-push-new-notes)
  :setq
  (anki-editor-latex-style . 'mathjax)
  :defer-config
  (defun +ensure-anki-editor-mode (note)
    "Ensure `anki-editor-mode' is enabled before pushing notes."
    (unless anki-editor-mode
      (anki-editor-mode 1)))
  (advice-add #'anki-editor--push-note :before #'+ensure-anki-editor-mode))

> image-slicing

(leaf image-slicing :ensure nil
  :hook org-mode-hook
  :setq
  (image-slicing-newline-trailing-text . nil))

> org-auto-tangle

(leaf org-auto-tangle
  :hook org-mode-hook)

> org-agenda

(leaf org-agenda :elpaca nil
  :after org
  :init
  (leader-bind
    "oa" 'org-agenda)

  :bind (org-agenda-mode-map
         (")" . 'org-agenda-todo))

  :config
  (setq org-todo-keywords
        '((sequence "TODO(t)" "NEXT(n)"
                    "|"
                    "DONE(d/!)")))
  (setq org-agenda-files
        (list ;; "~/Notes/org/Inbox.org"
         ;; "~/Notes/org/agenda.org"
         "~/Notes/denote/20250728T235116--todo__todo.org"))
  (defun +open-org-agenda-file ()
    (interactive)
    (find-file (car org-agenda-files)))

  (leader-bind
    "oo" '+open-org-agenda-file)

  (setq org-tag-alist
        '(;; Places
          ("@home"   . ?H)
          ("@school" . ?S)
          ;; ("@work" . ?W)
          ;; Activities
          ("@task" . ?t)
          ("@studying" . ?s)
          ("@errands"  . ?e)
          ("@tidy" . ?y)
          ("@creative" . ?c)
          ("@art" . ?a)
          ("@programming" . ?p)
          ("@today" . ?T)
          ;; ("@calls" . ?l)
          ;; Devices
          ("@phone" . ?P)
          ("@computer" . ?C)))
  (setq org-agenda-prefix-format
        `((agenda
           . ,(concat " %i "
                      "%?-12t"
                      "[%3(+org-get-prop-effort)]    "
                      ;; "%3(+org-get-prop-effort)  "
                      "% s"))
          (todo   . " %i ")
          (tags   . " %i %-12:c")
          ;; (search . " %i %-12:c")
          (search . " %c")
          ))

  (defun +org-get-prop-effort ()
    (if (not (eq major-mode 'org-mode)) ""
      (let ((val (org-entry-get nil "EFFORT")))
        (if (not val) ""
          (format "%s" (string-trim val))))))

  (require 'org-habit)
  (add-to-list 'org-modules 'org-habit t))

> org-super-agenda

(leaf org-super-agenda
  :after org-agenda
  :require t
  :config
  (org-super-agenda-mode 1)
  :setq
  (org-agenda-custom-commands
   . `(
       ("a" "main agenda"
        ((agenda ""
                 ((org-agenda-show-future-repeats nil)
                  (org-agenda-start-on-weekday nil)
                  (org-agenda-span 'week)
                  (org-habit-show-habits nil)
                  (org-agenda-skip-deadline-if-done t)
                  (org-agenda-skip-scheduled-if-done t)))
         (todo "NEXT")
         (agenda ""
                 ((org-agenda-span 1)
                  (org-agenda-use-time-grid nil)
                  (org-super-agenda-groups
                   '((:name none
                            :habit t)
                     (:discard (:anything t)))))))))))

> -- org-ql ----------------------------------------------------------------

(leaf org-ql
  :after org)

> org-pomodoro

(leaf org-pomodoro
  :after org)

> org-noter

(leaf org-noter
  :after org
  :bind (("C-c o n" . org-noter)
         ("C-c d n" . org-noter-start-from-dired)
         ("C-c o p" . +org-noter-set-prop-current-page))
  :setq
  (org-noter-doc-split-fraction . '(0.7 . 0.6))
  :config
  (defun +org-noter-set-prop-current-page (arg)
    "Set the property `NOTER_PAGE' of the current org heading to the current noter page.
The property will be removed if ran with a \\[universal-argument]."
    (interactive "P")
    (org-noter--with-selected-notes-window
     (if (equal arg '(4))
         (org-delete-property "NOTER_PAGE")
       (when-let ((vec (org-noter--get-current-view))
                  (num (and (vectorp vec)
                            (> (length vec) 1)
                            (format "%s" (aref vec 1)))))
         (message "meow: %s" num)
         (org-entry-put (point) "NOTER_PAGE" num))))))

> org-capture

(leaf org-capture :elpaca nil
  :after org
  :init
  (leader-bind
    "oc" 'org-capture)

  :config
  (defun +get-org-agenda-denote-file (name)
    (let ((regex (format "^.*--%s__.*\\.org$" name)))
      (car (seq-filter
            (lambda (path)
              (string-match regex (file-name-nondirectory path)))
            org-agenda-files))))

  (setq org-capture-templates
        `(("t" "Tasks")

          ("td" "Todo with deadline" entry
           (file ,(+get-org-agenda-denote-file "agenda"))
           "* TODO %^{Task}\nDEADLINE: %^{Deadline}t\n%?\n"
           :empty-lines 1
           :immediate-finish nil)

          ("tp" "Task" entry
           (file ,(+get-org-agenda-denote-file "agenda"))
           "* TODO %?\n  %U\n  %a\n  %i" :empty-lines 1)

          ("n" "New note (with Denote)" plain
           (file denote-last-path)
           #'denote-org-capture :no-save t :immediate-finish nil
           :kill-buffer t :jump-to-captured t))))

> more org-anki stuff

(defun +org-priority-to-anki ()
  (interactive)
  ;; check connection with anki
  (unless (or (boundp 'anki-editor-mode) anki-editor-mode)
    (anki-editor-mode 1))
  (anki-editor-api-check)
  ;; delete anki_note_type and/or anki_note_id for each w/o a priority
  (save-excursion
    (let ((points-no-priority
           (org-ql-query
             :select #'point-marker
             :from (current-buffer)
             :where
             '(and (not (priority))
                   (or (property "ANKI_NOTE_ID")
                       (property "ANKI_NOTE_TYPE"))))))
      (dolist (p (reverse points-no-priority))
        (goto-char p)
        (when (org-find-property "ANKI_NOTE_ID")
          (anki-editor-delete-note-at-point))
        (when (org-find-property "ANKI_NOTE_TYPE")
          (org-delete-property "ANKI_NOTE_TYPE")))))
  ;; ensure all priority headings have anki_note_type set
  (save-excursion
    (let ((points-yes-priority
           (org-ql-query
             :select #'point-marker
             :from (current-buffer)
             :where '(priority))))
      (dolist (p (reverse points-yes-priority))
        (goto-char p)
        (unless (org-entry-get nil "ANKI_NOTE_TYPE")
          (anki-editor-set-note-type nil "Basic"))))))

(defun +org-clone-with-fraction (days time effort)
  "Clone subtree with time shifts, prefixing each subheading with fraction prefix."
  (interactive
   (list
    (read-number "How many days to complete it over?: ")
    (read-number "How many minutes do you expect this task to take?: ")
    (read-number "On a scale of 1-10, how much effort will this take?: ")))
  (setq days (1- days))
  ;; create clones
  (org-clone-subtree-with-time-shift days "-1d")
  (org-set-property "TIME" (format "%s" time))
  (org-set-property "EFFORT" (format "%s" effort))
  ;; adjust appropriately
  (save-excursion
    (org-next-visible-heading 1)
    ;; first, sort
    (cl-loop for depth from (1- days) downto 1 do
             (save-excursion
               ;; shift
               (dotimes (_ depth)
                 (org-metadown))))
    ;; add todo and demote
    (save-excursion
      (cl-loop repeat (1- days) do
               (org-next-visible-heading 1))
      (cl-loop for depth from (1- days) downto 0 do
               (let ((frac (format "%d/%d" (1+ depth) days))
                     (time-daily (/ time days)))
                 (org-demote)
                 (let ((org-special-ctrl-a/e t))
                   (org-beginning-of-line))
                 (insert (concat frac " "))
                 (org-set-property "FRACTION" frac)
                 (org-set-property "TIME" (format "%s" time-daily))
                 (org-set-property "EFFORT" (format "%s" effort))
                 (org-next-visible-heading -1))))))

> visual fill column

(leaf visual-fill-column
  :require t
  :hook ((org-mode-hook . +org-visual-fill))
  :init
  (defun +org-visual-fill ()
    (setq visual-fill-column-width 100
          visual-fill-column-center-text t)
    (visual-fill-column-mode 1)))

% Org-modern

(leaf org-modern
  :init
  (global-org-modern-mode 1))

% end

(provide '+org)

% +org.el ends here


Last updated: August 22, 2025