Org-Mode Literate Config
The goal of my config is to keep the configuration distraction free, and focus on functionality over looks. If you are curious to see my notes and task repo, feel free to check out -> nmartin84/org-notes. Or you can checkout my published github pages -> nmartin84.github.io.
New Changes
Copy property value to kill-ring
Often times I will need to copy a property value from a task headline, and return its contents to the kill-ring. This function will enable you to do just that.
(defun nm/return-entry-property-names ()
"Returns a list of property names excluding the DEFAULT org-mode property names."
(let ((my-list (org-entry-properties))
(temp-list nil)
(discard-list '("ITEM" "PRIORITY" "FILE" "BLOCKED")))
(dolist (i my-list) (push (car i) temp-list))
(dolist (i discard-list) (setq temp-list (remove i temp-list)))
temp-list))
(nm/return-entry-property-names)
(defun nm/return-entry-property-value-kill-ring ()
"Returns a list of properties for the current headline and inserts the contents to the kill ring."
(interactive)
(let* ((choice (ivy-completing-read "entry: " (nm/return-entry-property-names)))
(results (org-entry-get nil choice)))
(kill-new results)
(message (format "'%s' was copied to kill-ring" results))))
(map! :after org
:map org-mode-map
:leader
:prefix ("z" . "nicks functions")
:desc "Copy property value to kill-ring" "x" #'nm/return-entry-property-value-kill-ring)
Classification System
For some time now, iโve been wanting to build a classification system for task items, to help structure context of a headline with a template of property fields that one would associate with that classifier. For example with books, when you are defining your new book organization you typically associate a book header with the following properties: Author, Subject, Year and maybe a few other things.
When you are building something like this out, it can become difficult or tideous to define those new items each time. So I have been giving some thought is a way to put a template around those items that remain original to org-mode and does not interfere.
(defun nm/return-entry-property-names ()
"Returns a list of property names excluding the DEFAULT org-mode property names."
(let ((my-list (org-entry-properties))
(temp-list nil)
(discard-list '("ITEM" "PRIORITY" "FILE" "BLOCKED")))
(dolist (i my-list) (push (car i) temp-list))
(dolist (i discard-list) (setq temp-list (remove i temp-list)))
temp-list))
(nm/return-entry-property-names)
(defun nm/return-entry-property-value-kill-ring ()
"Returns a list of properties for the current headline and inserts the contents to the kill ring."
(interactive)
(let ((choice (ivy-completing-read "entry: " (nm/return-entry-property-names))))
(kill-new (org-entry-get nil choice))))
(defun nm/org-clarify-properties ()
"Clarify properties for task."
(interactive)
(let ((my-list nm/org-clarify-templates)
(my-temp nil))
(dolist (i my-list) (push (car i) my-temp))
(dolist (i (cdr (assoc (ivy-completing-read "template: " my-temp) nm/org-clarify-templates))) (org-entry-put nil i (ivy-completing-read (format "%s: " i) (delete-dups (org-map-entries (org-entry-get nil i nil) nil 'file)))))))
(setq nm/org-clarify-templates '(("book" "AUTHOR" "YEAR" "SOURCE")
("online" "SOURCE" "SITE" "AUTHOR")
("purchase" "WHY" "FUNCTION")
("task" "AREA")
("project" "GOAL" "DUE")
("article" "SOURCE" "SITE" "SUBJECT")))
(map! :after org
:map org-mode-map
:leader
:prefix ("z" . "nicks functions")
:desc "Clarify Properties" "c" #'nm/org-clarify-properties)
(unless (ivy-completing-read "select: " '("Something"))
(error "no output"))
Bullet Journal
New capture template system that integrated the idea of a bullet journal for task and note taking.
(defun nm/capture-bullet-journal ()
"Finds bullet journal headline to nest capture headline under."
(let* ((date (format-time-string "%Y-%m-%d %a")))
(goto-char (point-min))
(unless (re-search-forward (format "^*+\s%s" date) nil t)
(goto-char (point-max))
(insert (format "* %s %s" date "[/]")))))
Org-Agenda Functions for Condition checks
So another pain point has been maintaining my task items. So iโm always looking for ways to tweak or adjust how I can automate or simply task management, and my agenda setup. So this next piece adds some functions to run condition checks for tasks that are in a NEXT state, or a Inbox or waiting to be defined state.
For this piece, iโve used bits and pieces from Doc Norangโs org-helper.el
. The
idea is to mimic the GTD methodology from the org-agenda view so all aspects of
it can be controled from the agenda.
(defun nm/skip-non-stuck-projects ()
"Skip trees that are not stuck projects."
(save-restriction
(widen)
(let ((next-headline (save-excursion (outline-next-heading))))
(if (bh/is-project-p)
(let* ((subtree-end (org-end-of-subtree t))
(has-next))
(save-excursion
(forward-line 1)
(while (and (not has-next) (< (point) subtree-end) (and (not (bh/is-project-p)) (nm/has-next-condition)))
(unless (member (or "WAITING" "SOMEDAY") (org-get-tags-at))
(setq has-next t))))
(if has-next
next-headline
nil))
next-headline))))
(defun nm/project-tasks-ready ()
"Skip trees that are not projects"
(let ((next-headline (save-excursion (outline-next-heading)))
(subtree-end (org-end-of-subtree t)))
(if (nm/skip-non-stuck-projects)
(cond
((and (bh/is-project-subtree-p) (nm/has-next-condition)) nil)
(t subtree-end))
subtree-end)))
(defun nm/has-next-condition ()
"Returns t if headline has next condition state"
(save-excursion
(cond
((nm/is-scheduled-p) t)
((nm/exist-context-tag-p) t)
((nm/checkbox-active-exist-p) t))))
Great, so that takes care of my project tasks that are ready to be worked, now I need something for standalone tasks.
(defun nm/standard-tasks-ready ()
"Show non-project tasks. Skip project and sub-project tasks, habits, and project related tasks."
(save-restriction
(widen)
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(cond
((bh/is-project-p) next-headline)
((bh/is-project-subtree-p) next-headline)
((and (bh/is-task-p) (not (nm/has-next-condition))) subtree-end)
(t nil)))))
For project tasks that are stuck:
(defun nm/stuck-projects ()
"Returns t when a project has no defined next actions for any of its subtasks."
(let ((next-headline (save-excursion (outline-next-heading)))
(subtree-end (org-end-of-subtree t)))
(if (or (bh/is-project-p) (bh/is-project-subtree-p))
(cond
((and (bh/is-project-subtree-p) (not (nm/has-next-condition))) nil))
subtree-end)))
For tasks that are ready to be refiled:
(defun nm/tasks-refile ()
"Returns t if the task is not part of a project and has no next state conditions."
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(next-heading (save-excursion (outline-next-heading))))
(cond
((nm/has-next-condition) next-heading)
((bh/is-project-p) subtree-end))))
Capturing to a Projects Timeframe
(defun nm/capture-project-timeframes ()
"Captures under the given projects timeframe headline."
(let ((p-name (ivy-completing-read "Select file: " (find-lisp-find-files "~/projects/orgmode/gtd/" "\.org$")))
(h-name "* Timeframe")
(c-name (read-string "Entry name: ")))
(goto-char (point-min))
(find-file p-name)
(unless (re-search-forward h-name nil t)
(progn (goto-char (point-max)) (newline) (insert "* Timeframe")))
(org-end-of-subtree t)
(newline 2)
(insert (format "** %s %s" (format-time-string "[%Y-%m-%d %a %H:%M]") c-name))
(newline)))
Easy theme switcher
(setq doom-dark-themes '("doom-one" "doom-solarized-dark" "doom-palenight" "doom-rouge" "doom-spacegrey" "doom-dracula" "doom-vibrant" "doom-city-lights" "doom-moonlight" "doom-horizon" "doom-old-hope" "doom-oceanic-next" "doom-monokai-pro" "doom-material" "doom-henna" "doom-gruvbox" "doom-ephemeral" "chocolate"))
(setq doom-light-themes '("doom-one-light" "doom-gruvbox-light" "doom-solarized-light" "doom-flatwhite"))
(defun nm/load-dark-theme ()
(interactive)
(let* ((themes doom-dark-themes)
(first (car doom-dark-themes)))
(counsel-load-theme-action (car themes))
(setq doom-theme (car themes))
(pop doom-dark-themes)
(add-to-list 'doom-dark-themes first t)))
(defun nm/load-light-theme ()
(interactive)
(let* ((themes doom-light-themes)
(first (car doom-light-themes)))
(counsel-load-theme-action (car themes))
(setq doom-theme (car themes))
(pop doom-light-themes)
(add-to-list 'doom-light-themes first t)))
Capturing Web Resources
;; This function was found on a stackoverflow post -> https://stackoverflow.com/questions/6681407/org-mode-capture-with-sexp
(defun get-page-title (url)
"Get title of web page, whose url can be found in the current line"
;; Get title of web page, with the help of functions in url.el
(with-current-buffer (url-retrieve-synchronously url)
;; find title by grep the html code
(goto-char 0)
(re-search-forward "<title>\\([^<]*\\)</title>" nil t 1)
(setq web_title_str (match-string 1))
;; find charset by grep the html code
(goto-char 0)
;; find the charset, assume utf-8 otherwise
(if (re-search-forward "charset=\\([-0-9a-zA-Z]*\\)" nil t 1)
(setq coding_charset (downcase (match-string 1)))
(setq coding_charset "utf-8")
;; decode the string of title.
(setq web_title_str (decode-coding-string web_title_str (intern
coding_charset))))
(concat "[[" url "][" web_title_str "]]")))
Task Automation
I got tired of manually setting task states to โNEXTโ โPROJโ โTODOโ so iโve added a few functions to automate this for me.
The NEXT state will be set if any of the following 3 conditions exist:
- Has a scheduled date assign to the task. (NOTE: We may want to add additional checks for expired task)
- Task has a context task assigned. (Context tags start with โ@โ)
- Has an active checkbox thatโs not marked completed.
The PROJ state will become active upon the following conditions:
- Task has a child headline with a TODO-KEYWORD of any-type (TODO/NEXT/WAIT).
DEPRECATED: I need to remove this section when I can.
ID Completion
I got tired of flipping back n forth between buffers, copying the ID of a
headline, then jump back and paste that ID into my link. So now iโll have
org-refile-get-location
fetch the ID for us when I call org-insert-link
.
NOTE: I need to see a better way of caching headlines to speed-up the process
(require 'find-lisp)
(defun nm/org-id-prompt-id ()
"Prompt for the id during completion of id: link."
(let* ((org-agenda-files (find-lisp-find-files org-directory "\.org$"))
(dest (org-refile-get-location))
(name nil)
(id nil))
(if (equal (last dest) '(nil))
(error "File contains no headlines")
(save-excursion
(find-file (cadr dest))
(goto-char (nth 3 dest))
(setq id (org-id-get (point) t)
name (org-get-heading t t t t)))
(org-insert-link nil (concat "id:" id) name))))
(after! org (org-link-set-parameters "id" :complete #'nm/org-id-prompt-id))
Quick Note Find Headline
Another pain point iโve been constantly facing is quickly taking a note and storing it in the appropriate location without losing focus on what iโm currently working on. This is common when on calls or meetings and you need to make a note for another task item youโre working.
So with that, I added a new function tied to the quick note capture-template key, which will prompt for a headline from any of your task files that exist in โ~~/projects/orgmode/gtd/~โ.
(defun nm/org-end-of-headline()
"Move to end of current headline"
(interactive)
(outline-next-heading)
(forward-char -1))
Daily Task Adder
This function will create a new headline, and nest the checkitem underneath the headline specified by DATE chosen during capture. This will also set a scheduled date on the headline. NOTE: Iโm not real sure this is of use anymore, maybe see if we should remove it.
(defun nm/org-capture-to-task-file ()
"Capture file to your default tasks file, and prompts to select a date where to file the task file to."
(let* ((child-l nil)
(parent "Checklists")
(date (org-read-date))
(heading (format "Items for")))
(goto-char (point-min))
;;; Locate or Create our parent headline
(unless (search-forward (format "* %s" parent) nil t)
(goto-char (point-max))
(newline)
(insert (format "* %s" parent))
(nm/org-end-of-headline))
(nm/org-end-of-headline)
;;; Capture outline level
(setq child-l (format "%s" (make-string (+ 1 (org-outline-level)) ?*)))
;;; Next we locate or create our subheading using the date string passed by the user.
(let* ((end (save-excursion (org-end-of-subtree t nil))))
(unless (re-search-forward (format "%s %s %s" child-l heading date) end t)
(newline 2)
(insert (format "%s %s %s %s" child-l heading date "[/]"))))))
Orgmode Formating
Eventually this will turn into a suite of functions that will clean-up the formatting of any org-mode document, and standardize to a common format that is believed to be standardized by the community. Reddit post will come eventually to discuss, and a link will be updated in this section when that comes.
TODO: I need to start building the outline for this.
(defun nm/add-newline-between-headlines ()
""
(when (equal major-mode 'org-mode)
(unless (org-at-heading-p)
(org-back-to-heading))
(nm/org-end-of-headline)
(if (not (org--line-empty-p 1))
(newline))))
(defun nm/add-space-end-of-line ()
"If N-1 at end of heading is #+end_src then insert blank character on last line."
(interactive)
(when (equal major-mode 'org-mode)
(unless (org-at-heading-p)
(org-back-to-heading))
(nm/org-end-of-headline)
(next-line -1)
(if (org-looking-at-p "^#\\+end_src$")
(progn (next-line 1) (insert " ")))))
(defun nm/newlines-between-headlines ()
"Uses the org-map-entries function to scan through a buffer's
contents and ensure newlines are inserted between headlines"
(interactive)
(org-map-entries #'nm/add-newline-between-headlines t 'file))
(add-hook 'org-insert-heading-hook #'nm/newlines-between-headlines)
Journal Capture Template
I need a way to make a dynamic template that will let me capture various types of information: meeting notes, calls, conversations, things iโm working on, etc.. Eventually this function will contain several mini templates inside of it that are called when initiated. TODO: Roll this under my new nm/find-file-or-create function.
(defun nm/capture-to-journal ()
"When org-capture-template is initiated, it creates the respected headline structure."
(let ((file "~/projects/orgmode/gtd/journal.org")
(parent nil)
(child nil))
(unless (file-exists-p file)
(with-temp-buffer (write-file file)))
(find-file file)
(goto-char (point-min))
;; Search for headline, or else create it.
(unless (re-search-forward "* Journal" nil t)
(progn (goto-char (point-max)) (newline) (insert "* Journal")))
(unless (re-search-forward (format "** %s" (format-time-string "%b '%y")) (save-excursion (org-end-of-subtree)) t)
(progn (org-end-of-subtree t) (newline) (insert (format "** %s" (format-time-string "%b '%y")))))))
Setting up my productivity layout
FIXME: I need to update this since I changed stuff around.
(defun nm/setup-productive-windows (arg1 arg2)
"Delete all other windows, and setup our ORGMODE production window layout."
(interactive)
(progn
(delete-other-windows)
(progn
(find-file arg1))
(progn
(split-window-right)
(evil-window-right 1)
(org-agenda nil "n"))
(progn
(split-window)
(evil-window-down 1)
(find-file arg2)
(goto-char 1)
(re-search-forward (format "*+\s\\w+\sTasks\sfor\s%s" (format-time-string "%Y-%m-%d")))
(org-tree-to-indirect-buffer))))
(defun nm/productive-window ()
"Setup"
(interactive)
(nm/setup-productive-windows "~/projects/orgmode/gtd/next.org" "~/projects/orgmode/gtd/tasks.org"))
(map! :after org
:map org-mode-map
:leader
:prefix ("TAB" . "workspace")
:desc "Load ORGMODE Setup" "," #'nm/productive-window)
Return Indirect Buffer
(defun nm/get-headlines-org-files (arg &optional indirect)
"Searches org-directory for headline and returns results to indirect buffer
ARG being a directory to search and optional INDIRECT should return t if you
want results returned to an indirect buffer."
(interactive)
(let* ((org-agenda-files (find-lisp-find-files arg "\.org$"))
(org-refile-use-outline-path 'file)
(org-refile-history nil)
(dest (org-refile-get-location))
(buffer nil)
(first (frame-first-window)))
(save-excursion
(if (eq first (next-window first))
(progn (evil-window-vsplit) (evil-window-right 1))
(other-window 1))
(find-file (cadr dest))
(goto-char (nth 3 dest))
(if indirect
(org-tree-to-indirect-buffer)
nil))))
(defun nm/search-headlines-org-directory ()
"Search the ORG-DIRECTORY, prompting user for headline and returns its results to indirect buffer."
(interactive)
(nm/get-headlines-org-files "~/projects/orgmode/"))
(defun nm/search-headlines-org-tasks-directory ()
"Search the GTD folder, prompting user for headline and returns its results to indirect buffer."
(interactive)
(nm/get-headlines-org-files "~/projects/orgmode/gtd/"))
(map! :after org
:map org-mode-map
:leader
:prefix ("s" . "search")
:desc "Outline Org-Directory" "c" #'nm/search-headlines-org-directory
:desc "Outline GTD directory" "!" #'nm/search-headlines-org-tasks-directory)
Requirements
These are some items that are required outside of the normal DOOM EMACS installation, before you can use this config. The idea here is to keep this minimum so as much of this is close to regular DOOM EMACS.
- SQLITE3 Installation: You will need to install sqlite3, typicalled installed via your package manager as
sudo apt install sqlite3
- I use a few different monospace fonts: Input, DejaVu, FiraCode, IBM Plex Mono and Roboto Mono.
Initial-Settings
These are some of the deafult settings that I like to get out of the way. This covers some basic things like my user profile name, email address and what information I like to dispaly in the modeline.
TODO: I need to change this heading to โHardened settingsโ and put in here things that typically never change.
(setq user-full-name "Nick Martin"
user-mail-address "[email protected]")
(display-time-mode 1)
(setq display-time-day-and-date t)
Next up, sometimes file changes are made outside of emacs, so if we detect changes I would like emacs to refert the buffer with those new changes. Also I like to have a bit more control in my undo actions and step through each change.
(global-auto-revert-mode 1)
(setq undo-limit 80000000
evil-want-fine-undo t
auto-save-default nil
inhibit-compacting-font-caches t)
(whitespace-mode -1)
(setq-default
delete-by-moving-to-trash t
tab-width 4
uniquify-buffer-name-style 'forward
window-combination-resize t
x-stretch-cursor nil)
Additional key bindings
(bind-key "<f6>" #'link-hint-copy-link)
(bind-key "<f12>" #'org-cycle-agenda-files)
(bind-key "M-." #'completion-at-point)
(map! :after org
:map org-mode-map
:leader
:prefix ("z" . "nicks functions")
:desc "completion at point" "c" #'completion-at-point
:desc "Review Fleeting Notes" "r" #'nm/review-fleeting-notes
:desc "Find File in ORGMODE" "f" #'nm/find-files-orgmode
:desc" File project" "p" #'nm/find-projects
:prefix ("s" . "+search")
:desc "Occur" "." #'occur
:desc "Outline" "o" #'counsel-outline
:desc "Counsel ripgrep" "d" #'counsel-rg
:desc "Swiper All" "@" #'swiper-all
:prefix ("l" . "+links")
"." #'org-next-link
"," #'org-previous-link
"o" #'org-open-at-point
"g" #'eos/org-add-ids-to-headlines-in-file)
(map! :after org-agenda
:map org-agenda-mode-map
:localleader
:desc "Filter" "f" #'org-agenda-filter)
(defun nm/review-fleeting-notes ()
(interactive)
(nm/find-file-cleaned-up "~/projects/orgmode/fleeting/"))
(defun nm/find-files-orgmode ()
(interactive)
(nm/find-file-cleaned-up org-directory))
(defun nm/find-projects ()
(interactive)
(nm/find-file-cleaned-up "~/projects/orgmode/gtd/projects/"))
If I ever use terminal mode, these are some settings iโll want to set to increase the quality of life when working from my terminal window.
(when (equal (window-system) nil)
(and
(bind-key "C-<down>" #'+org/insert-item-below)
;(setq doom-theme nil)
(setq doom-font (font-spec :family "Roboto Mono" :size 20))))
Now I add my default folders and files that I want emacs/org-mode to use:
(setq diary-file "~/projects/orgmode/diary.org")
(setq org-directory "~/projects/orgmode/")
(setq projectile-project-search-path "~/projects/")
Next we configure popup-rules and default fonts.
(after! org (set-popup-rule! "^\\*lsp-help" :side 'bottom :size .30 :select t)
(set-popup-rule! "*helm*" :side 'right :size .30 :select t)
(set-popup-rule! "*Org QL View:*" :side 'right :size .25 :select t)
(set-popup-rule! "*Org Note*" :side 'bottom :size .35 :select t)
(set-popup-rule! "*Capture*" :side 'left :size .30 :select t)
(set-popup-rule! "*Python:ob-ipython-py*" :side 'right :size .25 :select t)
(set-popup-rule! "*eww*" :side 'right :size .30 :select t)
(set-popup-rule! "*CAPTURE-*" :side 'left :size .30 :select t))
;(set-popup-rule! "*Org Agenda*" :side 'right :size .35 :select t))
(setq inhibit-compacting-font-caches t)
(setq doom-font (font-spec :family "IBM Plex Mono" :size 24 :weight 'light)
doom-big-font (font-spec :family "IBM Plex Mono" :size 26 :weight 'light)
doom-variable-pitch-font (font-spec :family "IBM Plex Mono" :weight 'regular :size 20)
doom-serif-font (font-spec :family "IBM Plex Mono" :weight 'light))
(when (equal window-system 'x) (toggle-frame-fullscreen))
(after! org
(custom-set-faces!
'(org-level-1 :height 1.15 :inherit outline-1)
'(org-level-2 :height 1.13 :inherit outline-2)
'(org-level-3 :height 1.11 :inherit outline-3)
'(org-level-4 :height 1.09 :inherit outline-4)
'(org-level-5 :height 1.07 :inherit outline-5)
'(org-level-6 :height 1.05 :inherit outline-6)
'(org-level-7 :height 1.03 :inherit outline-7)
'(org-level-8 :height 1.01 :inherit outline-8)))
(after! org
(custom-set-faces!
'(org-document-title :height 1.15)))
;; (when (equal system-type 'gnu/linux)
;; (setq doom-font (font-spec :family "JetBrains Mono" :size 20 :weight 'normal)
;; doom-big-font (font-spec :family "JetBrains Mono" :size 22 :weight 'normal)))
;; (when (equal system-type 'windows-nt)
;; (setq doom-font (font-spec :family "InputMono" :size 18)
;; doom-big-font (font-spec :family "InputMono" :size 22)))
Org-Mode
Here we add any requirements before org-mode starts to load. Some key notes here to make note of:
- org-image-actual-width will use the function to try and set the image size to a % of your displayโs width.
- hl-todo-mode enabled for ORGMODE. All of my notes are stored under my parent org-directory along with my GTD tasks, but I donโt necesarily like to add tasks to my notes files, or see them appear in my org-agenda. So instead I add the keywords where I need to make a note, or something I need to follow-up on and use the magit-todos-list to see what follow-up items I have to complete. HACK: See if we can modify hl-todo keywords just for ORGMODE.
NOTE [2021-01-03 Sun] - iโm going to try swapping log notes going after drawers to see how I like itโฆ
(require 'org-habit)
(require 'org-id)
(require 'org-checklist)
(after! org (setq org-archive-location "~/projects/orgmode/gtd/archives.org::* %s"
;org-image-actual-width (truncate (* (display-pixel-width) 0.15))
org-link-file-path-type 'relative
org-log-state-notes-insert-after-drawers t
org-catch-invisible-edits 'error
org-refile-targets '((nil :maxlevel . 9)
(org-agenda-files :maxlevel . 4))
org-refile-use-outline-path 'buffer-name
org-refile-use-cache nil
org-outline-path-complete-in-steps nil
org-refile-allow-creating-parent-nodes 'confirm
org-startup-indented 'indent
org-insert-heading-respect-content t
org-startup-folded 'content
org-src-tab-acts-natively t
org-list-allow-alphabetical nil))
(add-hook 'org-mode-hook 'auto-fill-mode)
;(add-hook 'org-mode-hook 'hl-todo-mode)
(add-hook 'org-mode-hook (lambda () (display-line-numbers-mode -1)))
(setq org-attach-directory (concat org-directory ".attach/"))
Is there a way to prevent auto-fill from breaking links?
Agenda
First we setup a few deafults for the org-agenda buffer:
(setq org-agenda-todo-ignore-scheduled nil
org-agenda-tags-todo-honor-ignore-options t
org-agenda-fontify-priorities t)
This first stage is how I track whatโs on my list of things to complete.
(setq org-agenda-custom-commands nil)
(push '("o" "overview"
((agenda ""
((org-agenda-span '1)
(org-agenda-overriding-header "๎ฃ Agenda")
(org-agenda-files (append (file-expand-wildcards "~/projects/orgmode/gtd/*.org")))
(org-agenda-start-day (org-today))))
(tags-todo "-@delegated-someday/+NEXT"
((org-agenda-overriding-header "๎ Next Tasks")
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t)
(org-tags-match-list-sublevels 'indented)
(org-agenda-sorting-strategy
'(category-up))))
(tags-todo "-@delegated-someday/+DOING"
((org-agenda-overriding-header "๎ข Doing")
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t)
(org-tags-match-list-sublevels 'indented)
(org-agenda-sorting-strategy
'(category-up))))
(tags-todo "@errands-someday/!-REFILE-NEXT-DOING"
((org-agenda-overriding-header "๎ฏ Errands")
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t)
(org-tags-match-list-sublevels 'indented)
(org-agenda-sorting-strategy
'(category-up))))
(tags-todo "@home-someday/!-REFILE-NEXT-DOING"
((org-agenda-overriding-header "๎ข Home")
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t)
(org-tags-match-list-sublevels 'indented)
(org-agenda-sorting-strategy
'(category-up))))
(tags-todo "@computer-someday/!-REFILE-NEXT-DOING"
((org-agenda-overriding-header "๎ Computer")
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t)
(org-tags-match-list-sublevels 'indented)
(org-agenda-sorting-strategy
'(category-up))))
(tags-todo "-{^@\\w+}-someday/-NEXT-REFILE-READ-DOING"
((org-agenda-overriding-header "๎กฑ Other Tasks")
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t)
(org-tags-match-list-sublevels 'indented)
(org-agenda-sorting-strategy
'(category-up))))
(tags-todo "-someday/+REFILE"
((org-agenda-overriding-header "๎
จ Inbox"))))) org-agenda-custom-commands)
(push '("gh" "@home" tags-todo "@home/-REFILE") org-agenda-custom-commands)
(push '("ge" "@errands" tags-todo "@errands/-REFILE") org-agenda-custom-commands)
(push '("gc" "@computer" tags-todo "@computer/-REFILE") org-agenda-custom-commands)
(push '("gr" "@read" tags-todo "@read/-REFILE") org-agenda-custom-commands)
(push '("gd" "doing" todo "+DOING") org-agenda-custom-commands)
(push '("gn" "next" todo "+NEXT") org-agenda-custom-commands)
(push '("go" "other tasks" tags-todo "-{^@\\w+}-goals/-NEXT-REFILE-DOING" ((org-agenda-todo-ignore-with-date t))) org-agenda-custom-commands)
(push '("gg" "goals" tags-todo "goals/" ((org-agenda-todo-ignore-with-date t))) org-agenda-custom-commands)
(push '("gi" "๎
จ inbox" todo "REFILE") org-agenda-custom-commands)
(push '("l" "literature"
((tags-todo "/!"
((org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-with-date t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t)
(org-agenda-files (append (find-lisp-find-files "~/projects/orgmode/literature/" "\.org$")))
(org-tags-match-list-sublevels 'indented))))) org-agenda-custom-commands)
(push '("r" "review"
((tags-todo "-{^@\\w+}/-REFILE"
((org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-with-date t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t))))) org-agenda-custom-commands)
(push '("b" "bullet"
((agenda ""
((org-agenda-span '2)
(org-agenda-files (append (file-expand-wildcards "~/projects/orgmode/bullet/*.org")))
(org-agenda-start-day (org-today))))
(tags-todo "-someday/"
((org-agenda-overriding-header "Task Items")
(org-agenda-files (append (file-expand-wildcards "~/projects/orgmode/bullet/*.org")))
(org-agenda-todo-ignore-scheduled t)
(org-agenda-todo-ignore-deadlines t)
(org-agenda-todo-ignore-with-date t)))
(tags "note"
((org-agenda-overriding-header "Notes")
(org-agenda-files (append (file-expand-wildcards "~/projects/orgmode/bullet/*.org"))))))) org-agenda-custom-commands)
Capture Templates
What templates do I need available for quick capture of information?
- Checklists
- Bullet Journal
- Journal
- Notes
- Web Resources
Task items can be a few different things, and thereโs the whole GTD which iโm trying my bestest to follow. Sometimes I may have a task item that I simply need to remind myself to complete, and just need to check it off a list acknowledging iโve completed it and other times I need an actual task item to capture and track data in.
(setq org-capture-templates '(("g" "๎ gtd")
("gp" "๎ projects")
("b" "๎ bullet journal")
("l" "๎ local project")
("n" "๎ก notes")
("r" "๎ผ resources")))
(push '("gpt" "๎ task" entry (function nm/find-project-task) "* REFILE %^{task}\n%?" :empty-lines-before 1 :empty-lines-after 1) org-capture-templates)
(push '("gpr" "๎ define requirements" item (function nm/find-project-requirement) "" :empty-lines-before 1 :empty-lines-after 1) org-capture-templates)
(push '("gpn" "๎ฃ note" entry (function nm/find-project-note) "* " :empty-lines-before 1 :empty-lines-after 1) org-capture-templates)
(push '("gpf" "๎ฃ timeframe" entry (function nm/find-project-timeframe) "* %^{timeframe entry} [%<%Y-%m-%d %a %H:%M>]\n:PROPERTIES:\n:CREATED: %U\n:END:\n%?" :empty-lines-before 1 :empty-lines-after 1) org-capture-templates)
;; TODO: Cleanup the template names to be more clear and easier to recognize.
(push '("ga" "๎ฏ append to headline" plain (function nm/org-capture-log) "๎ฃ *Note added:* [%<%Y-%m-%d %a %H:%M>]\n%?" :empty-lines-before 1 :empty-lines-after 1) org-capture-templates)
(push '("gc" "๎
จ capture" entry (file+olp "~/projects/orgmode/gtd/tasks.org" "Inbox") "* REFILE %^{task}\n:PROPERTIES:\n:CREATED: %U\n:END:\n:METADATA:\n- SOURCE:\n- AUTHOR:\n:END:\n%?") org-capture-templates)
(push '("gk" "๎
capture [kill-ring]" entry (file+olp "~/projects/orgmode/gtd/tasks.org" "Inbox") "* REFILE %^{task}\n:PROPERTIES:\n:CREATED: %U\n:END:\n%c") org-capture-templates)
(push '("gx" "๎ณ capture [current pos]" entry (file+olp "~/projects/orgmode/gtd/tasks.org" "Inbox") "* REFILE %^{task}\n:PROPERTIES:\n:CREATED: %U\n:END:\nLocation at time of capture: %a") org-capture-templates)
(defun nm/prompt-during-capture ()
"Prompt and ask for metadata properties during capture."
(interactive)
(when (y-or-n-p "add schedule? ")
(insert (format "SCHEDULED: <%s>"(org-read-date)))))
;; TODO: I need to finish implementing the bullet-journal.
(push '("bt" "๎ bullet task" entry (file+function "~/projects/orgmode/gtd/bullet.org" nm/capture-bullet-journal) "* REFILE %^{task} %^g\n:PROPERTIES:\n:CREATED: %U\n:END:\n" :empty-lines-before 1 :empty-lines-after 1) org-capture-templates)
(push '("nj" "๎กฅ journal" entry (function nm/capture-to-journal) "* %^{entry}\n:PROPERTIES:\n:CREATED: %U\n:END:\n%?") org-capture-templates)
(push '("nn" "๎ข new reference [excluded from org-roam]" plain (function nm/create-notes-file) "%?" :unnarrowed t :empty-lines-before 1 :empty-lines-after 1) org-capture-templates)
(push '("nr" "๎ฏ roam article" plain (function nm/create-roam-file)"%?" :unnarrowed t) org-capture-templates)
;; TODO: Configure more resource capture templates.
(push '("rr" "๎ research literature" entry (file+function "~/projects/orgmode/gtd/websources.org" nm/enter-headline-websources) "* READ %(get-page-title (current-kill 0))") org-capture-templates)
(push '("rf" "๎ฅ rss feed" entry (file+function "~/projects/orgmode/elfeed.org" nm/return-headline-in-file) "* %^{link}") org-capture-templates)
;; This function is used in conjuction with the capture template "new note" which will find or generate a note based off the folder and filename.
(defun nm/create-notes-file ()
"Function for creating a notes file under org-capture-templates."
(nm/find-file-or-create t "~/projects/orgmode/references/" "note"))
(defun nm/create-roam-file ()
"Function to create a new roam notes file, while prompting for folder location."
(nm/find-file-or-create t org-directory "note"))
(defun nm/find-project-task ()
"Function for creating a project file under org-capture-templates."
(nm/find-file-or-create t "~/projects/orgmode/gtd/projects" "project" "Tasks")
(setq org-agenda-files (append (file-expand-wildcards "~/projects/orgmode/gtd/*.org") (file-expand-wildcards "~/projects/orgmode/gtd/*/*.org"))))
(defun nm/find-project-timeframe ()
"Function for creating a project file under org-capture-templates."
(nm/find-file-or-create t "~/projects/orgmode/gtd/projects" "project" "Timeframe"))
(defun nm/find-project-requirement ()
"Function for creating a project file under org-capture-templates."
(nm/find-file-or-create t "~/projects/orgmode/gtd/projects" "project" "Requirements"))
(defun nm/find-project-note ()
"Function for creating a project file under org-capture-templates."
(nm/find-file-or-create t "~/projects/orgmode/gtd/projects" "project" "Notes"))
(defun nm/return-headline-in-file ()
"Returns the headline position."
(let* ((org-agenda-files "~/projects/orgmode/elfeed.org")
(location (nth 3 (org-refile-get-location nil nil 'confirm))))
(goto-char location)
(org-end-of-line)))
(defun nm/find-project-todo ()
"When in projectile path, finds root todo.org file"
(let ((path (doom-project-root))
(file "todo.org"))
(find-file (format "%s%s" path file))))
(defun nm/enter-headline-websources ()
"This is a simple function for the purposes when using org-capture to add my entries to a custom Headline, and if URL is not in clipboard it'll return an error and cancel the capture process."
(let* ((file "~/projects/orgmode/gtd/websources.org")
(headline (read-string "Headline? ")))
(progn
(nm/check-headline-exist file headline)
(goto-char (point-min))
(re-search-forward (format "^\*+\s%s" (upcase headline))))))
(defun nm/check-headline-exist (file-arg headline-arg)
"This function will check if HEADLINE-ARG exists in FILE-ARG, and if not it creates the headline."
(save-excursion (find-file file-arg) (goto-char (point-min))
(unless (re-search-forward (format "* %s" (upcase headline-arg)) nil t)
(goto-char (point-max)) (insert (format "* %s" (upcase headline-arg))) (org-set-property "CATEGORY" (downcase headline-arg)))) t)
(defun nm/org-capture-log ()
"Initiate the capture system and find headline to capture under."
(let* ((org-agenda-files (find-lisp-find-files "~/projects/orgmode/gtd/" "\.org$"))
(dest (org-refile-get-location))
(file (cadr dest))
(pos (nth 3 dest))
(title (nth 2 dest)))
(find-file file)
(goto-char pos)
(nm/org-end-of-headline)))
Clock Settings
(after! org (setq org-clock-continuously t)) ; Will fill in gaps between the last and current clocked-in task.
Default Tags
I donโt like to shift my eyes back n forth when iโm scanning data, so I keep my columns one space after the headline.
(setq org-tags-column 0)
I like to keep a list of predefined context tags, this helps speed the assignment process up and also keep things consistent.
(setq org-tag-alist '(("@home" . ?h)
("@computer" . ?c)
("@errands")
("@read")
("@delegated")
("someday")))
Export Settings
(after! org (setq org-html-head-include-scripts t
org-export-with-toc t
org-export-with-author t
org-export-headline-levels 4
org-export-with-drawers nil
org-export-with-email t
org-export-with-footnotes t
org-export-with-sub-superscripts nil
org-export-with-latex t
org-export-with-section-numbers nil
org-export-with-properties nil
org-export-with-smart-quotes t
org-export-backends '(pdf ascii html latex odt md pandoc)))
Embed images into the exported HTML files.
(defun replace-in-string (what with in)
(replace-regexp-in-string (regexp-quote what) with in nil 'literal))
(defun org-html--format-image (source attributes info)
(progn
(setq source (replace-in-string "%20" " " source))
(format "<img src=\"data:image/%s;base64,%s\"%s />"
(or (file-name-extension source) "")
(base64-encode-string
(with-temp-buffer
(insert-file-contents-literally source)
(buffer-string)))
(file-name-nondirectory source))))
Keywords
After much feedback and discussing with other users, I decided to simplify the keyword list to make it simple. Defining a project will now focus on the tag word :project: so that all child task are treated as part of the project.
Keyword | Description |
---|---|
\TODO | Task has actionable items defined and ready to be worked. |
HOLD | Has actionable items, but is on hold due to various reasons. |
NEXT | Is ready to be worked and should be worked on soon. |
DONE | Task is completed and closed. |
KILL | Abandoned or terminated. |
(custom-declare-face '+org-todo-next '((t (:inherit (bold font-lock-constant-face org-todo)))) "")
(custom-declare-face '+org-todo-project '((t (:inherit (bold font-lock-doc-face org-todo)))) "")
(custom-declare-face '+org-todo-onhold '((t (:inherit (bold warning org-todo)))) "")
(custom-declare-face '+org-todo-next '((t (:inherit (bold font-lock-keyword-face org-todo)))) "")
(custom-declare-face 'org-checkbox-statistics-todo '((t (:inherit (bold font-lock-constant-face org-todo)))) "")
(setq org-todo-keywords
'((sequence
"REFILE(r)" "TODO(t)" "NEXT(n)" "DOING(o)" "WAIT(w)" "|" "DONE(d)" "KILL(k)")
(sequence
"PROJ(p)" "|" "COMPLETE" "CANCELED"))
org-todo-keyword-faces
'(("WAIT" . +org-todo-onhold)
("DOING" . +org-todo-active)
("NEXT" . +org-todo-next)
("REFILE" . +org-todo-onhold)
("PROJ" . +org-todo-next)
("TODO" . +org-todo-active)))
Loading agenda settings
(after! org (setq org-agenda-diary-file "~/projects/orgmode/diary.org"
org-agenda-dim-blocked-tasks nil ; This has funny behavior, similar to checkbox dependencies.
org-agenda-use-time-grid nil
org-agenda-tags-column 0
org-agenda-hide-tags-regexp "^w+" ; Hides tags in agenda-view
org-agenda-compact-blocks nil
org-agenda-block-separator " "
org-agenda-skip-scheduled-if-done t
org-agenda-skip-deadline-if-done t
org-agenda-window-setup 'current-window
org-enforce-todo-checkbox-dependencies nil ; This has funny behavior, when t and you try changing a value on the parent task, it can lead to Emacs freezing up. TODO See if we can fix the freezing behavior when making changes in org-agenda-mode.
org-enforce-todo-dependencies t
org-habit-show-habits t))
(after! org (setq org-agenda-files (append (file-expand-wildcards "~/projects/orgmode/gtd/*.org") (file-expand-wildcards "~/projects/orgmode/gtd/*/*.org"))))
Logging and Drawers
Next, we like to keep a history of our activity of a task so we track when
changes occur, and we also keep our notes logged in their own drawer. Optionally
you can also add the following in-buffer settings to override the
org-log-into-drawer
function. #+STARTUP: logdrawer
or #+STARTUP: nologdrawer
(after! org (setq org-log-into-drawer t
org-log-done 'time
org-log-repeat 'time
org-log-redeadline 'note
org-log-reschedule 'note))
Looks and Feels
Here we change some of the things how org-mode looks and feels, some options available are:
- org-ellipsis =โโผ, โด, โฌ, โคท,โฆ, and โฑ.โ=
- org-superstar-headline-bullets-list =โโโ โโโ โโโ โโดโ โโขโ=
(after! org (setq org-hide-emphasis-markers t
org-hide-leading-stars t
org-list-demote-modify-bullet '(("+" . "-") ("1." . "a.") ("-" . "+"))))
(when (require 'org-superstar nil 'noerror)
(setq org-superstar-headline-bullets-list '("โ")
org-superstar-item-bullet-alist nil))
(when (require 'org-fancy-priorities nil 'noerror)
(setq org-fancy-priorities-list '("โ" "โ" "โฌ")))
Properties
I like to have properties inherited from their parent.
(after! org (setq org-use-property-inheritance t))
Publishing
REVIEW do we need to re-define our publish settings for the ROAM directory?
(after! org (setq org-publish-project-alist
'(("attachments"
:base-directory "~/projects/orgmode/"
:recursive t
:base-extension "jpg\\|jpeg\\|png\\|pdf\\|css\\|svg"
:publishing-directory "~/projects/nmartin84.github.io"
:publishing-function org-publish-attachment)
("notes"
:base-directory "~/projects/orgmode/"
:base-extension "org"
:publishing-directory "~/projects/nmartin84.github.io"
:section-numbers nil
:with-properties nil
:with-drawers nil
:with-timestamps active
:with-creator t
:with-email t
:with-toc t
:recursive t
:exclude "gtd/secrets.org|gtd/journal.org"
:headline-levels 8
:auto-sitemap t
:sitemap-filename "index.html"
:publishing-function org-html-publish-to-html
:html-head "<link rel=\"stylesheet\" href=\"https://raw.githack.com/nmartin84/html-style-sheets/master/notes.css\" type=\"text/css\"/>"
:html-link-up "../"
:html-link-up "../../index.html"
:auto-preamble t)
("myprojectweb" :components("attachments" "notes")))))
Modules
Org-Appear is a cool package thatโll auto show emphasis markers when your cursor is on a element enclosed in emphasis.
(add-hook 'org-mode-hook 'org-appear-mode)
(setq org-appear-autolinks nil)
company mode
;(setq company-backends '(company-capf))
(set-company-backend! 'org-mode '(company-yasnippet company-capf company-files company-elisp))
(set-company-backend! 'emacs-lisp-mode '(company-yasnippet company-elisp))
(setq company-idle-delay 0.25
company-minimum-prefix-length 2)
(add-to-list 'company-backends '(company-capf company-files company-yasnippet company-semantic company-bbdb company-cmake company-keywords))
Deadgrep
(map! :map deadgrep-mode-map
"o" #'deadgrep-visit-result-other-window)
DEFT
When this variable is set to t
your deft directory will be updated to your
projectile-project rootโs folder when switching projects, and the deft bufferโs
contents will be refreshed.
Configuring DEFT default settings
(use-package deft
:bind (("<f8>" . deft))
:commands (deft deft-open-file deft-new-file-named)
:config
(setq deft-directory "~/projects/orgmode/"
deft-auto-save-interval 0
deft-recursive t
deft-current-sort-method 'title
deft-extensions '("md" "txt" "org")
deft-use-filter-string-for-filename t
deft-use-filename-as-title nil
deft-markdown-mode-title-level 1
deft-file-naming-rules '((nospace . "-"))))
(defun my-deft/strip-quotes (str)
(cond ((string-match "\"\\(.+\\)\"" str) (match-string 1 str))
((string-match "'\\(.+\\)'" str) (match-string 1 str))
(t str)))
(defun my-deft/parse-title-from-front-matter-data (str)
(if (string-match "^title: \\(.+\\)" str)
(let* ((title-text (my-deft/strip-quotes (match-string 1 str)))
(is-draft (string-match "^draft: true" str)))
(concat (if is-draft "[DRAFT] " "") title-text))))
(defun my-deft/deft-file-relative-directory (filename)
(file-name-directory (file-relative-name filename deft-directory)))
(defun my-deft/title-prefix-from-file-name (filename)
(let ((reldir (my-deft/deft-file-relative-directory filename)))
(if reldir
(concat (directory-file-name reldir) " > "))))
(defun my-deft/parse-title-with-directory-prepended (orig &rest args)
(let ((str (nth 1 args))
(filename (car args)))
(concat
(my-deft/title-prefix-from-file-name filename)
(let ((nondir (file-name-nondirectory filename)))
(if (or (string-prefix-p "README" nondir)
(string-suffix-p ".txt" filename))
nondir
(if (string-prefix-p "---\n" str)
(my-deft/parse-title-from-front-matter-data
(car (split-string (substring str 4) "\n---\n")))
(apply orig args)))))))
(provide 'my-deft-title)
(advice-add 'deft-parse-title :around #'my-deft/parse-title-with-directory-prepended)
Elfeed
(use-package elfeed-org
:defer
:config
(setq rmh-elfeed-org-files (list "~/projects/orgmode/elfeed.org")))
(use-package elfeed
:defer
:config
(setq elfeed-db-directory "~/.elfeed/"))
(require 'elfeed-org)
(elfeed-org)
(setq elfeed-db-directory "~/.elfeed/")
(setq rmh-elfeed-org-files (list "~/.elfeed/elfeed.org"))
Graphs and Chart Modules
Eventually I would like to have org-mind-map generating charts like Sachaโs evil-plans.
(after! org (setq org-ditaa-jar-path "~/.emacs.d/.local/straight/repos/org-mode/contrib/scripts/ditaa.jar"))
(use-package gnuplot
:defer
:config
(setq gnuplot-program "gnuplot"))
; MERMAID
(use-package mermaid-mode
:defer
:config
(setq mermaid-mmdc-location "~/node_modules/.bin/mmdc"
ob-mermaid-cli-path "~/node_modules/.bin/mmdc"))
; PLANTUML
(use-package ob-plantuml
:ensure nil
:commands
(org-babel-execute:plantuml)
:defer
:config
(setq plantuml-jar-path (expand-file-name "~/.doom.d/plantuml.jar")))
Hugo
(setq hugo_base_dir "~/projects/braindump/")
Journal
(after! org (setq org-journal-dir "~/projects/orgmode/gtd/journal/"
org-journal-enable-agenda-integration t
org-journal-file-type 'monthly
org-journal-carryover-items "TODO=\"TODO\"|TODO=\"NEXT\"|TODO=\"PROJ\"|TODO=\"STRT\"|TODO=\"WAIT\"|TODO=\"HOLD\""))
Magit Mode
A touch of magit to make it a little more special.
(add-to-list 'magit-todos-keywords-list "NOTE")
Noter
(setq org-noter-notes-search-path (concat org-directory "literature/"))
Pandoc
(setq org-pandoc-options '((standalone . t) (self-contained . t)))
Reveal
(when (require 'ox-reveal nil 'noerror)
(setq org-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js")
(setq org-reveal-title-slide nil))
ROAM
These are my default ROAM settings
(when (require 'org-roam nil 'noerror)
(setq org-roam-tag-sources '(prop all-directories))
(setq org-roam-db-location "~/projects/orgmode/roam.db")
(setq org-roam-directory "~/projects/orgmode/")
(setq org-roam-buffer-position 'right)
(setq org-roam-link-file-path-type 'relative)
(setq org-roam-file-exclude-regexp "references/*\\|gtd/*\\|elfeed.org\\|README.org")
(setq org-roam-completion-everywhere t)
;; Configuration of daily templates
(setq org-roam-dailies-capture-templates
'(("d" "daily" plain (function org-roam-capture--get-point) ""
:immediate-finish t
:file-name "journal/%<%Y-%m-%d-%a>"
:head "#+TITLE: %<%Y-%m-%d %a>\n#+STARTUP: content\n\n")))
(setq org-roam-capture-templates
'(("l" "literature" plain (function org-roam-capture--get-point)
:file-name "literature/%<%Y%m%d%H%M>-${slug}"
:head "#+title: ${title}\n#+author: %(concat user-full-name)\n#+email: %(concat user-mail-address)\n#+created: %(format-time-string \"[%Y-%m-%d %H:%M]\")\n#+roam_tags: %^{roam_tags}\n\nsource: \n\n%?"
:unnarrowed t)
("f" "fleeting" plain (function org-roam-capture--get-point)
:file-name "fleeting/%<%Y%m%d%H%M>-${slug}"
:head "#+title: ${title}\n#+author: %(concat user-full-name)\n#+email: %(concat user-mail-address)\n#+created: %(format-time-string \"[%Y-%m-%d %H:%M]\")\n#+roam_tags:\n\n%?"
:unnarrowed t)
("p" "Permanent (prompt folder)" plain (function org-roam-capture--get-point)
:file-name "%(read-directory-name \"directory: \" org-directory)/%<%Y%m%d%H%M>-${slug}"
:head "#+title: ${title}\n#+author: %(concat user-full-name)\n#+email: %(concat user-mail-address)\n#+created: %(format-time-string \"[%Y-%m-%d %H:%M]\")\n#+roam_tags:\n\n%?"
:unnarrowed t)))
(push '("x" "Projects" plain (function org-roam-capture--get-point)
:file-name "gtd/projects/%<%Y%m%d%H%M>-${slug}"
:head "#+title: ${title}\n#+roam_tags: %^{tags}\n\n%?"
:unnarrowed t) org-roam-capture-templates))
(defun nm/org-roam-prompt-tags ()
"Prompt user and ask if they want to input roam_tags during capture."
(when (y-or-n-p "Add tags? ")
(insert (format "%s" "\n#+roam_tags: "))))
ROAM Server
(when (require 'org-roam-server nil 'noerror)
(use-package org-roam-server
:ensure t
:config
(setq org-roam-server-host "192.168.1.249"
org-roam-server-port 8060
org-roam-server-export-inline-images t
org-roam-server-authenticate nil
org-roam-server-network-poll t
org-roam-server-network-vis-options "{ \"layout\": { \"randomSeed\": false }, \"physics\": { \"stabilization\": { \"iterations\": 10000, \"fit\": false, \"updateInterval\": 10000 }, \"barnesHut\": { \"gravitationalConstant\": -4000, \"avoidOverlap\": 1, \"springConstant\": 0.02, \"springLength\": 95 } } }"
org-roam-server-network-arrows nil
org-roam-server-serve-files t
org-roam-server-extra-node-options (list (cons 'shape "dot") (cons 'opacity 1))
org-roam-server-network-label-truncate t
org-roam-server-network-label-truncate-length 40
org-roam-server-network-label-wrap-length 20)))
Look into abbrev-mode
Custom Functions
(load! "org-helpers.el")
(defun nm/task-is-active-proj ()
"Checks if task is a Project with child subtask"
(and (bh/is-project-p)
(nm/has-subtask-active-p)))
(defun nm/task-is-stuck-proj ()
"Checks if task is a Project with child subtask"
(and (bh/is-project-p)
(not (nm/has-subtask-active-p))))
(defun nm/has-subtask-active-p ()
"Returns t for any heading that has subtasks."
(save-restriction
(widen)
(org-back-to-heading t)
(let ((end (save-excursion (org-end-of-subtree t))))
(outline-end-of-heading)
(save-excursion
(re-search-forward (concat "^\*+ " "\\(NEXT\\|DOING\\)") end t)))))
(defun nm/update-task-conditions ()
"Update task states depending on their conditions."
(interactive)
(org-map-entries (lambda ()
(when (nm/task-is-active-proj) (org-todo "ACTIVE"))
(when (nm/task-is-stuck-proj) (org-todo "PENDING"))) t))
(add-hook 'before-save-hook #'nm/update-task-conditions)
(defun nm/find-file-cleaned-up (folder)
"Returns a list of filenames, in a cleaned up format and easy to read. FOLDER will
be your folder path to search for."
(interactive)
(let* ((files (find-lisp-find-files folder ".org$"))
(files-alist nil)
(file-names nil))
(dolist (i files) (push (cons i (capitalize (replace-regexp-in-string "[-_]" " " (replace-regexp-in-string "^[0-9]+-\\|.org$" "" (file-name-nondirectory i))))) files-alist))
(dolist (i files-alist) (push (cdr i) file-names))
(let* ((choice (ivy-completing-read "select: " file-names)))
(if (equal choice "")
nil
(find-file (car (rassoc choice files-alist)))))))
(defun nm/convert-filename-format (&optional time-p folder-path)
"Prompts user for filename and directory, and returns the value in a cleaned up format.
If TIME-P is t, then includes date+time stamp in filename, FOLDER-PATH is the folder
location to search for files."
(let* ((file (replace-in-string " " "-" (downcase (read-file-name "select file: " (if folder-path (concat folder-path) org-directory))))))
(if (file-exists-p file)
(concat file)
(if (s-ends-with? ".org" file)
(concat (format "%s%s" (file-name-directory file) (if time-p (concat (format-time-string "%Y%m%d%H%M%S-") (file-name-nondirectory (downcase file)))
(concat (file-name-nondirectory (downcase file))))))
(concat (format "%s%s.org" (file-name-directory file) (if time-p (concat (format-time-string "%Y%m%d%H%M%S-") (file-name-nondirectory (downcase file)))
(concat (file-name-nondirectory (downcase file))))))))))
(defun nm/find-file-or-create (time-p folder-path &optional type header)
"Creates a new file, if TYPE is set to NOTE then also insert file-template."
(interactive)
(let* ((file (nm/convert-filename-format time-p folder-path))) ;; TODO: Add condition when filename is passed in as argument to skip this piece.
(if (file-exists-p file)
(find-file file)
(when (equal "note" type) (find-file file)
(insert (format "%s\n%s\n%s\n\n"
(downcase (format "#+title: %s" (replace-in-string "-" " " (replace-regexp-in-string "[0-9]+-" "" (replace-in-string ".org" "" (file-name-nondirectory file))))))
(downcase (concat "#+author: " user-full-name))
(downcase (concat "#+email: " user-mail-address)))))
(when (equal "project" type) (find-file file)
(insert (format "%s\n%s\n%s\n\n* Requirements\n\n* Timeframe\n\n* Notes\n\n* Tasks\n"
(downcase (format "#+title: %s" (replace-in-string "-" " " (replace-regexp-in-string "[0-9]+-" "" (replace-in-string ".org" "" (file-name-nondirectory file))))))
(downcase (concat "#+author: " user-full-name ))
(downcase (concat "#+email: " user-mail-address)))))
(when (equal nil type) (find-file)))
;; If user passes in header argument, search for it and if the search fails to find the header, create it.
(if header (unless (progn (goto-char (point-min)) (re-search-forward (format "^*+ %s" header)))
(goto-char (point-max))
(newline)
(insert (format "* %s" header))
(newline)))))
Archive keeping Structure
(defadvice org-archive-subtree (around fix-hierarchy activate)
(let* ((fix-archive-p (and (not current-prefix-arg)
(not (use-region-p))))
(location (org-archive--compute-location org-archive-location))
(afile (car location))
(offset (if (= 0 (length (cdr location)))
1
(1+ (string-match "[^*]" (cdr location)))))
(buffer (or (find-buffer-visiting afile) (find-file-noselect afile))))
ad-do-it
(when fix-archive-p
(with-current-buffer buffer
(goto-char (point-max))
(while (> (org-current-level) offset) (org-up-heading-safe))
(let* ((olpath (org-entry-get (point) "ARCHIVE_OLPATH"))
(path (and olpath (split-string olpath "/")))
(level offset)
tree-text)
(when olpath
(org-mark-subtree)
(setq tree-text (buffer-substring (region-beginning) (region-end)))
(let (this-command) (org-cut-subtree))
(goto-char (point-min))
(save-restriction
(widen)
(-each path
(lambda (heading)
(if (re-search-forward
(rx-to-string
`(: bol (repeat ,level "*") (1+ " ") ,heading)) nil t)
(org-narrow-to-subtree)
(goto-char (point-max))
(unless (looking-at "^")
(insert "\n"))
(insert (make-string level ?*)
" "
heading
"\n"))
(cl-incf level)))
(widen)
(org-end-of-subtree t t)
(org-paste-subtree level tree-text))))))))
Custom Faces
TODO: Consider moving to extras.org if we are not using it.
;; (defface org-logbook-note
;; '((t (:foreground "LightSkyBlue")))
;; "Face for printr function")
;; (custom-set-faces!
;; '(org-roam-block-link :weight "bold"))
;; (font-lock-add-keywords 'org-mode '(("\\[\\[\\[\\[.+\\]\\[.+\\]\\]\\]\\]" . 'org-roam-block-link)))
Clarify Tasks
TODO: I need to either figure something out for this, or move it to extra.org
Clarify task will take a list of property fields and pass them to
nm/org-clarify-task-properties
to update task items which are missing those
property fields.
(defun nm/org-get-headline-property (arg)
"Extract property from headline and return results."
(interactive)
(org-entry-get nil arg t))
(defun nm/org-get-headline-properties ()
"Get headline properties for ARG."
(org-back-to-heading)
(org-element-at-point))
(defun nm/org-get-headline-title ()
"Get headline title from current headline."
(interactive)
(org-element-property :title (nm/org-get-headline-properties)))
;;;;;;;;;;;;--------[ Clarify Task Properties ]----------;;;;;;;;;;;;;
(defun nm/org-clarify-metadata ()
"Runs the clarify-task-metadata function with ARG being a list of property values." ; TODO work on this function and add some meaning to it.
(interactive)
(nm/org-clarify-task-properties org-tasks-properties-metadata))
(map! :after org
:map org-mode-map
:localleader
:prefix ("j" . "nicks functions")
:desc "Clarify properties" "c" #'nm/org-clarify-metadata)
(defun nm/emacs-change-font ()
"Change font based on available font list."
(interactive)
(let ((font (ivy-completing-read "font: " nm/font-family-list))
(size (ivy-completing-read "size: " '("16" "18" "20" "22" "24" "26" "28" "30")))
(weight (ivy-completing-read "weight: " '(normal light bold extra-light ultra-light semi-light extra-bold ultra-bold)))
(width (ivy-completing-read "width: " '(normal condensed expanded ultra-condensed extra-condensed semi-condensed semi-expanded extra-expanded ultra-expanded))))
(setq doom-font (font-spec :family font :size (string-to-number size) :weight (intern weight) :width (intern width))
doom-big-font (font-spec :family font :size (+ 2 (string-to-number size)) :weight (intern weight) :width (intern width))))
(doom/reload-font))
(defvar nm/font-family-list '("JetBrains Mono" "Roboto Mono" "VictorMono Nerd Font Mono" "Fira Code" "Hack" "Input Mono" "Anonymous Pro" "Cousine" "PT Mono" "DejaVu Sans Mono" "Victor Mono" "Liberation Mono"))
End of file loading
Load secrets from hereโฆ
(let ((secrets (expand-file-name "secrets.el" doom-private-dir)))
(when (file-exists-p secrets)
(load secrets)))