From 040fcfcffacc33d2235cf3915e86891f44c22625 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Thu, 31 Oct 2019 22:45:59 -0400 Subject: [PATCH] lang/org: add centralized org-capture project targets For saving project todos/notes/changelogs in a central {org-directory}/projects.org file, under {Project Name}/{Tasks,Notes,Changelog} headings. If you want to prefix the outline path, you can specific a :parents property. e.g. (after! org-capture (org-capture-put :parents '("Projects"))) or (dolist (key '("ot" "on" "oc")) (setf (alist-get key org-capture-templates) (append (alist-get key org-capture-templates) '(:parents ("Projects"))))) Also sets :kill-buffer t by default, for all org capture templates. --- modules/lang/org/autoload/org-capture.el | 71 ++++++++++++++++++++---- modules/lang/org/config.el | 44 ++++++++++++--- 2 files changed, 95 insertions(+), 20 deletions(-) diff --git a/modules/lang/org/autoload/org-capture.el b/modules/lang/org/autoload/org-capture.el index c43345a28..9cbdeb4f0 100644 --- a/modules/lang/org/autoload/org-capture.el +++ b/modules/lang/org/autoload/org-capture.el @@ -76,15 +76,6 @@ you're done. This can be called from an external shell script." ;; ;;; Capture targets -(defun +org--capture-root (path) - (let ((filename (file-name-nondirectory path))) - (expand-file-name - filename - (or (locate-dominating-file (file-truename default-directory) - filename) - (doom-project-root) - (user-error "Couldn't detect a project"))))) - ;;;###autoload (defun +org-capture-todo-file () "Expand `+org-capture-todo-file' from `org-directory'. @@ -97,21 +88,77 @@ If it is an absolute path return `+org-capture-todo-file' verbatim." If it is an absolute path return `+org-capture-todo-file' verbatim." (expand-file-name +org-capture-notes-file org-directory)) +(defun +org--capture-local-root (path) + (let ((filename (file-name-nondirectory path))) + (expand-file-name + filename + (or (locate-dominating-file (file-truename default-directory) + filename) + (doom-project-root) + (user-error "Couldn't detect a project"))))) + ;;;###autoload (defun +org-capture-project-todo-file () "Find the nearest `+org-capture-todo-file' in a parent directory, otherwise, opens a blank one at the project root. Throws an error if not in a project." - (+org--capture-root +org-capture-todo-file)) + (+org--capture-local-root +org-capture-todo-file)) ;;;###autoload (defun +org-capture-project-notes-file () "Find the nearest `+org-capture-notes-file' in a parent directory, otherwise, opens a blank one at the project root. Throws an error if not in a project." - (+org--capture-root +org-capture-notes-file)) + (+org--capture-local-root +org-capture-notes-file)) ;;;###autoload (defun +org-capture-project-changelog-file () "Find the nearest `+org-capture-changelog-file' in a parent directory, otherwise, opens a blank one at the project root. Throws an error if not in a project." - (+org--capture-root +org-capture-changelog-file)) + (+org--capture-local-root +org-capture-changelog-file)) + +(defun +org--capture-ensure-heading (headings &optional initial-level) + (if (not headings) + (widen) + (let ((initial-level (or initial-level 1))) + (if (and (re-search-forward (format org-complex-heading-regexp-format + (regexp-quote (car headings))) + nil t) + (= (org-current-level) initial-level)) + (progn + (beginning-of-line) + (org-narrow-to-subtree)) + (goto-char (point-max)) + (unless (and (bolp) (eolp)) (insert "\n")) + (insert (make-string initial-level ?*) + " " (car headings) "\n") + (beginning-of-line 0)) + (+org--capture-ensure-heading (cdr headings) (1+ initial-level))))) + +(defun +org--capture-central-file (file project) + (let ((file (expand-file-name +org-capture-projects-file org-directory))) + (set-buffer (org-capture-target-buffer file)) + (org-capture-put-target-region-and-position) + (widen) + (goto-char (point-min)) + ;; Find or create the project headling + (+org--capture-ensure-heading + (append (org-capture-get :parents) + (list project (org-capture-get :heading)))))) + +;;;###autoload +(defun +org-capture-central-project-todo-file () + "TODO" + (+org--capture-central-file + +org-capture-todo-file (projectile-project-name))) + +;;;###autoload +(defun +org-capture-central-project-notes-file () + "TODO" + (+org--capture-central-file + +org-capture-notes-file (projectile-project-name))) + +;;;###autoload +(defun +org-capture-central-project-changelog-file () + "TODO" + (+org--capture-central-file + +org-capture-changelog-file (projectile-project-name))) diff --git a/modules/lang/org/config.el b/modules/lang/org/config.el index 0e4b6e06d..05da22aeb 100644 --- a/modules/lang/org/config.el +++ b/modules/lang/org/config.el @@ -39,6 +39,9 @@ target file. Is relative to `org-directory', unless it is absolute. Is used in Doom's default `org-capture-templates'.") +(defvar +org-capture-projects-file "projects.org" + "Default, centralized target for org-capture templates.") + (defvar +org-initial-fold-level 2 "The initial fold level of org files when no #+STARTUP options for it.") @@ -241,25 +244,50 @@ I like: org-capture-templates '(("t" "Personal todo" entry (file+headline +org-capture-todo-file "Inbox") - "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) + "* TODO %?\n%i\n%a" :prepend t) ("n" "Personal notes" entry (file+headline +org-capture-notes-file "Inbox") - "* %u %?\n%i\n%a" :prepend t :kill-buffer t) + "* %u %?\n%i\n%a" :prepend t) ;; Will use {project-root}/{todo,notes,changelog}.org, unless a ;; {todo,notes,changelog}.org file is found in a parent directory. ;; Uses the basename from `+org-capture-todo-file', ;; `+org-capture-changelog-file' and `+org-capture-notes-file'. ("p" "Templates for projects") - ("pt" "Project todo" entry ; {project-root}/todo.org + ("pt" "Project-local todo" entry ; {project-root}/todo.org (file+headline +org-capture-project-todo-file "Inbox") - "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) - ("pn" "Project notes" entry ; {project-root}/notes.org + "* TODO %?\n%i\n%a" :prepend t) + ("pn" "Project-local notes" entry ; {project-root}/notes.org (file+headline +org-capture-project-notes-file "Inbox") - "* TODO %?\n%i\n%a" :prepend t :kill-buffer t) - ("pc" "Project changelog" entry ; {project-root}/changelog.org + "* %?\n%i\n%a" :prepend t) + ("pc" "Project-local changelog" entry ; {project-root}/changelog.org (file+headline +org-capture-project-changelog-file "Unreleased") - "* TODO %?\n%i\n%a" :prepend t :kill-buffer t))) + "* %?\n%i\n%a" :prepend t) + + ;; Will use {org-directory}/{+org-capture-projects-file} and store + ;; these under {ProjectName}/{Tasks,Notes,Changelog} headings. They + ;; support `:parents' to specify what headings to put them under, e.g. + ;; :parents ("Projects") + ("o" "Centralized templates for projects") + ("ot" "Project todo" entry + (function +org-capture-central-project-todo-file) + "* TODO %?\n %i\n %a" + :heading "Tasks" + :prepend nil) + ("on" "Project notes" entry + (function +org-capture-central-project-notes-file) + "* %?\n %i\n %a" + :heading "Notes" + :prepend t) + ("oc" "Project changelog" entry + (function +org-capture-central-project-changelog-file) + "* %?\n %i\n %a" + :heading "Changelog" + :prepend t))) + + ;; Kill capture buffers by default (unless they've been visited) + (after! org-capture + (org-capture-put :kill-buffer t)) (defadvice! +org--capture-expand-variable-file-a (file) "If a variable is used for a file path in `org-capture-template', it is used