Org is a package that allows you to create files like this one that look nice while also being able to run code. In this file, the code being run is stored in code blocks and all other text is disregarded.

* Org Setup

This installs the org package and creates a setup function to enable/disable certain functionalities.

#+begin_src emacs-lisp
;; Setup org mode
(defun jm/org-mode-setup ()
  (org-indent-mode)
  (flyspell-mode)
  (variable-pitch-mode 1)
  (visual-line-mode 1)
  (add-to-list 'org-link-frame-setup '(file . find-file))) ; Open link in current window not other window.

(use-package org
  :hook (org-mode . jm/org-mode-setup)
  :config
  (setq jm/inbox-file "~/Seafile/org/inbox.org"
        org-ellipsis " ▾"
        org-hide-emphasis-markers t
        org-src-preserve-indentation t

        ;; Default export configuration
        org-export-with-toc nil
        org-export-with-section-numbers nil
        org-export-with-sub-superscripts nil
        org-latex-src-block-backend 'listings
        org-format-latex-options (plist-put org-format-latex-options :scale 1.3)

        ;; Org agenda timestamp formatting
        org-display-custom-times t
        org-time-stamp-custom-formats '("%m-%d-%y %a" . "%m-%d-%y %a %I:%M %p")

        ;; Capture templates
        org-capture-templates '(("i" "Inbox" entry (file jm/inbox-file) "* %?\n")))

  (jm/leader-keys
    "oa" '(org-agenda :which-key "Org agenda")
    "oc" '(org-capture :which-key "Org capture")
    "oi" '((lambda () (interactive) (find-file jm/inbox-file)) :which-key "Open inbox file")))
#+end_src

* Cosmetics

** Org-Bullets

Org-bullets is a package that adds bullets to each heading instead of asterisks. It just makes org files nicer to look at.

#+begin_src emacs-lisp
;; Org-bullets for better headings
(use-package org-bullets
  :after org
  :hook (org-mode . org-bullets-mode)
  :custom
  (org-bullets-bullet-list '("◉" "○" "●" "○" "●" "○" "●")))
#+end_src

** Add List Dots

By default lists are started with a hyphen, though this doesn't really match the aesthetic of the rest of the org file. Due to that, I added this line which replaces the hyphen with a dot.

- Bullet point 1
- Bullet point 2
- Bullet point 3

#+begin_src emacs-lisp
;; Replace - lists with a dot
(font-lock-add-keywords 'org-mode
                        '(("^ *\\([-]\\) "
                           (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•"))))))
#+end_src

** Font Changes

Org-faces changes the font size of the headings to make them bigger in the org file. Though this code also changes the font to a variable-pitch font. To make it so that only fixed-pitch fonts are used in things like code blocks, *set-face-attribute* is used below.

#+begin_src emacs-lisp
(with-eval-after-load 'org-faces
  ;; Set faces for heading size levels
  (dolist (face '((org-level-1 . 1.2)
                  (org-level-2 . 1.1)
                  (org-level-3 . 1.05)
                  (org-level-4 . 1.0)
                  (org-level-5 . 1.0)
                  (org-level-6 . 1.0)
                  (org-level-7 . 1.0)
                  (org-level-8 . 1.0)))
    (set-face-attribute (car face) nil :font "Fira Code Light" :weight 'regular :height (cdr face)))

  ;; Ensure that anything that should be fixed-pitch in Org files appears that way
  (set-face-attribute 'org-block nil    :foreground nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-table nil    :inherit 'fixed-pitch)
  (set-face-attribute 'org-formula nil  :inherit 'fixed-pitch)
  (set-face-attribute 'org-code nil     :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-table nil    :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-checkbox nil  :inherit 'fixed-pitch)
  (set-face-attribute 'line-number nil :inherit 'fixed-pitch)
  (set-face-attribute 'line-number-current-line nil :inherit 'fixed-pitch))
#+end_src

** Visual Fill Column

Visual fill column is a package that allows you to center text and add borders to the sides of an org file. By default org files are displayed completely to the left side of the page like normal text files.

#+begin_src emacs-lisp
;; Set left-right margins with visual-fill-column
(defun jm/org-mode-visual-fill ()
  (setq visual-fill-column-width 160
        visual-fill-column-center-text t)
  (visual-fill-column-mode 1))

(use-package visual-fill-column
  :after org
  :hook (org-mode . jm/org-mode-visual-fill))
#+end_src

** Syntax Highlighting in Latex Exports

By default the syntax highlighting for latex/PDF documents exported with Emacs is pretty lackluster. To improve this, I can install the ~engrave-faces~ package.

#+begin_src emacs-lisp
(use-package engrave-faces
  :after ox-latex
  :config
  (setq org-latex-src-block-backend 'engraved))
#+end_src

* Evil Org

By default, many of the org specific keybindings do not feel intuitive when using evil mode. ~evil-org~ is a package that attempts to fix this by rebinding many of the default org keybindings to work better with Evil mode.

#+begin_src emacs-lisp
(use-package evil-org
  :after org
  :hook (org-mode . (lambda () evil-org-mode))
  :config
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys))
#+end_src

* Org Agenda

Org mode by default contains an /agenda/ system which is like a basic calendar that allows you to schedule todo items from org documents. All of the todo items from each org document are stored in a central area to allow for a formation of an /agenda/.

#+begin_src emacs-lisp
(setq org-agenda-start-with-log-mode t)
(setq org-log-done 'time)
(setq org-log-into-drawer t)

(setq org-priority-default ?D)
(setq org-priority-lowest ?D)
#+end_src

** Refresh Org Agenda Files

Creates a function to refresh the ~org-agenda-files~ variable to be set to include all org roam notes files.

#+begin_src emacs-lisp
(defun jm/org-roam-refresh-agenda-list ()
  (interactive)
  (let ((directory (expand-file-name org-roam-dailies-directory org-roam-directory)))
    (setq org-agenda-files
          (seq-filter
           (lambda (file-path) (not (s-starts-with-p directory file-path)))
           (org-roam-list-files)))))
#+end_src

To use this function, I'll add it to some hooks to make sure to run it before the agenda list is needed.

#+begin_src emacs-lisp
(advice-add 'org-agenda :before #'jm/org-roam-refresh-agenda-list)
(advice-add 'org-todo-list :before #'jm/org-roam-refresh-agenda-list)
(advice-add 'dashboard-get-agenda :before #'jm/org-roam-refresh-agenda-list)
#+end_src

** Custom States

Adds custom states to tasks such as ~IN PROGRESS~, ~CANCELLED~, etc. I've also added some changes to the color of the todo items based on the status. For example, turning the task face orange if it is /in progress/.

#+begin_src emacs-lisp
(setq org-todo-keyword-faces '(("IN PROGRESS" . (:foreground "orange" :weight bold))
                               ("WAITING" . (:foreground "yellow" :weight bold))))

(setq org-todo-keywords
      '((sequence
         "TODO(t)"
         "IN PROGRESS(i)"
         "WAITING(w)"
         "|"
         "DONE(d)"
         "CANCELLED(c)"
         "FAILED(f)")))

(setq org-clock-in-switch-to-state "IN PROGRESS")
#+end_src

** Refresh Checkboxes

Adds a hook to repeated tasks in org agenda that, when repeated, checkboxes will be reset to an unchecked state.

#+begin_src emacs-lisp
(add-hook 'org-todo-repeat-hook #'org-reset-checkbox-state-subtree)
#+end_src

** Automatically Add Todo UUIDs

#+begin_src emacs-lisp
(add-hook 'org-after-todo-state-change-hook
        (lambda () (when (org-get-todo-state)
                        (org-id-get-create))))
#+end_src

* Org Roam

Org-roam is a plain-text knowledge management system. It brings some of Roam's more powerful features into the Org-mode ecosystem such as org-file linking, etc.

** Custom Indexing Functions

Before getting into the main config for Org-roam, I've created a few functions for better indexing nodes stored in the org-roam database. Specifically, these functions separate the org roam dailies nodes from other nodes.

#+begin_src emacs-lisp
(defun jm/org-roam-find-filter (node)
  (let ((directory (expand-file-name org-roam-dailies-directory org-roam-directory)))
    (string= (file-name-directory (org-roam-node-file node))
             directory)))

(defun jm/org-roam-dailies-find ()
  (interactive)
  (org-roam-node-find nil nil #'jm/org-roam-find-filter))

(defun jm/org-roam-find ()
  (interactive)
  (org-roam-node-find
   nil nil
   (lambda (node) (not (jm/org-roam-find-filter node)))))
#+end_src

** Main Config

Below is the main config for org-roam.

#+begin_src emacs-lisp
(use-package org-roam
  :init (require 'org-roam-dailies)
  :custom
  (org-roam-directory (file-truename "~/Nextcloud/org"))
  (org-roam-node-display-template "${title:60} ${file-title}")
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . jm/org-roam-find)
         ("C-c n d" . jm/org-roam-dailies-find)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
         ("C-c n o" . org-id-get-create)
         ("C-c n t" . jm/org-roam-capture-day))
  :config
  ;; Config for org-roam capture templates.
  (setq jm/org-roam-templates-directory (expand-file-name "templates" org-roam-directory)
        org-roam-capture-templates
        `(("d" "Default" plain "%?"
           :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
           :unnarrowed t)
          ("p" "Project" plain
           (file ,(expand-file-name "project.org" jm/org-roam-templates-directory))
           :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                              "#+title: ${title}\n")
           :unnarrowed t)
          ("y" "12 Week Year" plain
           (file ,(expand-file-name "12-week-year.org" jm/org-roam-templates-directory))
           :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                              "#+title: ${title}\n")
           :unnarrowed t)))
  (setq org-roam-dailies-capture-templates
        `(("d" "Default" entry "* %?"
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n"))
          ("r" "Reflection" entry
           (file ,(expand-file-name "reflection.org" jm/org-roam-templates-directory))
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n")
           :unnarrowed t)
          ("S" "Weekly Scorecard" entry
           (file ,(expand-file-name "weekly-scorecard.org" jm/org-roam-templates-directory))
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n")
           :unnarrowed t)
          ("s" "Daily Scorecard" entry
           (file ,(expand-file-name "daily-scorecard.org" jm/org-roam-templates-directory))
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n")
           :unnarrowed t)
          ("t" "Todos" entry
           (file ,(expand-file-name "daily.org" jm/org-roam-templates-directory))
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n")
           :unnarrowed t)))

  ;; Create org roam directory and templates directory if not found.
  (unless (file-directory-p org-roam-directory)
    (make-directory org-roam-directory))
  (unless (file-directory-p jm/org-roam-templates-directory)
    (make-directory jm/org-roam-templates-directory))

  (org-roam-db-autosync-mode))
#+end_src

* Fix Snippets

This is required as of org 9.2 as snippets such as ~<s~ don't work. Without this fix, you are required to manually type out structures like code blocks.

#+begin_src emacs-lisp
  (require 'org-tempo)
#+end_src

* Ispell

Configure ~Ispell~ to not spell check certain org mode specific keywords. By default, ~Ispell~ doesn't have a default dictionary selected. This can be changed by running ~ispell-change-dictionary~. If no dictionaries are found, you can install them from the ~hunspell-en_us~ pacman package.

#+begin_src emacs-lisp
(add-to-list 'ispell-skip-region-alist '(":\\(PROPERTIES\\|LOGBOOK\\):" . ":END:")) ;
(add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_SRC" . "#\\+END_SRC"))
(add-to-list 'ispell-skip-region-alist '("~" . "~"))
(add-to-list 'ispell-skip-region-alist '("\\[\\[" . "\\]"))
#+end_src
