emacs: add work-progress-code to select a project via org-capture

This commit is contained in:
Protesilaos Stavrou 2024-11-24 16:35:14 +02:00
parent 514515179b
commit dc99ff49fd
No known key found for this signature in database
GPG key ID: 99BD6459CD5CA3EA
3 changed files with 247 additions and 21 deletions

View file

@ -279,6 +279,71 @@
:immediate-finish t
:empty-lines 1)))
(setq org-capture-templates
`(("u" "Unprocessed" entry
(file+headline "tasks.org" "Unprocessed")
,(concat "* %^{Title}\n"
":PROPERTIES:\n"
":CAPTURED: %U\n"
":END:\n\n"
"%a\n%i%?")
:empty-lines-after 1)
("p" "Projects in general" entry
(file+headline "tasks.org" "Projects")
,(concat "* TODO %^{Title} %^g\n"
"%^{How time sensitive it is|SCHEDULED|SCHEDULED|DEADLINE}: %^t\n"
":PROPERTIES:\n"
":CAPTURED: %U\n"
":END:\n\n"
"%a%?")
:empty-lines-after 1)
("s" "Add to an existing project" entry
(function prot-org-capture-select-project)
,(concat "* TODO %^{Title} %^g\n"
"%^{How time sensitive it is|SCHEDULED|SCHEDULED|DEADLINE}: %^t\n"
":PROPERTIES:\n"
":CAPTURED: %U\n"
":END:\n\n"
"%a%?")
:empty-lines-after 1)
("p" "Projects in general" entry
(file+headline "tasks.org" "Projects")
,(concat "* TODO %^{Title} %^g\n"
"%^{How time sensitive it is|SCHEDULED|SCHEDULED|DEADLINE}: %^t\n"
":PROPERTIES:\n"
":CAPTURED: %U\n"
":END:\n\n"
"%a%?")
:empty-lines-after 1)
("c" "Clock in and do immediately" entry
(file+headline "tasks.org" "Clocked tasks")
,(concat "* TODO %^{Title}\n"
":PROPERTIES:\n"
":EFFORT: %^{Effort estimate in minutes|5|10|15|30|45|60|90|120}\n"
":END:\n\n"
"%a\n")
:prepend t
:clock-in t
:clock-keep t
:immediate-finish t
:empty-lines-after 1)
("p" "Private lesson or service" entry
(file "coach.org")
#'prot-org-capture-coach
:prepend t
:empty-lines 1)
("P" "Private service clocked" entry
(file+headline "coach.org" "Clocked services")
#'prot-org-capture-coach-clock
:prepend t
:clock-in t
:clock-keep t
:immediate-finish t
:empty-lines 1)))
;; NOTE 2024-11-10: I realised that I was not using this enough, so
;; I decided to simplify my setup. Keeping it here, in case I need
;; it again.

View file

@ -7710,7 +7710,7 @@ As for my workflow, here is an overview:
:bind ("C-c c" . org-capture)
:config
(require 'prot-org)
(setq org-capture-templates
`(("u" "Unprocessed" entry
(file+headline "tasks.org" "Unprocessed")
@ -7720,17 +7720,25 @@ As for my workflow, here is an overview:
":END:\n\n"
"%a\n%i%?")
:empty-lines-after 1)
;; ("e" "Email note (unprocessed)" entry ; Also see `org-capture-templates-contexts'
;; (file+headline "tasks.org" "Unprocessed")
;; ,(concat "* TODO %:subject :mail:\n"
;; ":PROPERTIES:\n"
;; ":CAPTURED: %U\n"
;; ":END:\n\n"
;; "%a\n%i%?")
;; :empty-lines-after 1)
("w" "Add to the wishlist (may do some day)" entry
(file+headline "tasks.org" "Wishlist")
,(concat "* %^{Title}\n"
("p" "Projects in general" entry
(file+headline "tasks.org" "Projects")
,(concat "* TODO %^{Title} %^g\n"
"%^{How time sensitive it is|SCHEDULED|SCHEDULED|DEADLINE}: %^t\n"
":PROPERTIES:\n"
":CAPTURED: %U\n"
":END:\n\n"
"%a%?")
:empty-lines-after 1)
("s" "Add to an existing project" plain
(function prot-org-capture-select-project)
"TODO %?"
:immediate-finish t) ; My custom function does all the work
("p" "Projects in general" entry
(file+headline "tasks.org" "Projects")
,(concat "* TODO %^{Title} %^g\n"
"%^{How time sensitive it is|SCHEDULED|SCHEDULED|DEADLINE}: %^t\n"
":PROPERTIES:\n"
":CAPTURED: %U\n"
":END:\n\n"
@ -7748,15 +7756,6 @@ As for my workflow, here is an overview:
:clock-keep t
:immediate-finish t
:empty-lines-after 1)
("t" "Time-sensitive task" entry
(file+headline "tasks.org" "Tasks with a date")
,(concat "* TODO %^{Title} %^g\n"
"%^{How time sensitive it is|SCHEDULED|SCHEDULED|DEADLINE}: %^t\n"
":PROPERTIES:\n"
":CAPTURED: %U\n"
":END:\n\n"
"%a%?")
:empty-lines-after 1)
("p" "Private lesson or service" entry
(file "coach.org")
#'prot-org-capture-coach
@ -14186,6 +14185,87 @@ DEADLINE: %%^T
(advice-add 'org-capture-place-template :around 'prot-org--capture-no-delete-windows)
(advice-add 'org-add-log-note :around 'prot-org--capture-no-delete-windows)
;;;;; Custom function to select a project to add to
(defun prot-org--get-outline (&optional file)
"Return `outline-regexp' headings and line numbers of current file or FILE."
(with-current-buffer (find-file-noselect file)
(let ((outline-regexp (format "^\\(?:%s\\)" (or (bound-and-true-p outline-regexp) "[*\^L]+")))
candidates)
(save-excursion
(goto-char (point-min))
(while (if (bound-and-true-p outline-search-function)
(funcall outline-search-function)
(re-search-forward outline-regexp nil t))
(push
;; NOTE 2024-11-24: The -5 (minimum width) is a sufficiently high number to keep the
;; alignment consistent in most cases. Larger files will simply shift the heading text
;; in minibuffer, but this is not an issue anymore.
(format "%-5s\t%s"
(line-number-at-pos (point))
(buffer-substring-no-properties (line-beginning-position) (line-end-position)))
candidates)
(goto-char (1+ (line-end-position)))))
(if candidates
(nreverse candidates)
(user-error "No outline")))))
(defvar prot-org-outline-history nil
"Minibuffer history for `prot-org-outline-prompt'.")
(defun prot-org-outline-prompt (&optional file)
"Prompt for outline among headings retrieved by `prot-org--get-outline'.
With optional FILE use the outline of it, otherwise use that of
the current file."
(let ((current-file (or file buffer-file-name))
(default (car prot-org-outline-history)))
(completing-read
(format-prompt
(format "Select heading inside `%s': "
(propertize (file-name-nondirectory current-file) 'face 'error))
default)
(prot-common-completion-table-no-sort 'imenu (prot-org--get-outline current-file))
nil :require-match nil 'prot-org-outline-history default)))
(defvar prot-org-file-history nil
"Minibuffer history of `prot-org-file-prompt'.")
(defun prot-org--not-useful-p (file)
"Return non-nil if FILE is not a useful Org file for `org-capture'."
(or (string-match-p "\\.org_archive\\'" file)
(backup-file-name-p file)
(not (string-match-p "\\.org\\'" file))))
(defun prot-org-file-prompt ()
"Select a file in the `org-directory'."
(if-let ((dir org-directory)
(files (directory-files-recursively org-directory ".*" nil))
(files (seq-remove #'prot-org--not-useful-p files)))
(let ((default (car prot-org-file-history)))
(completing-read
(format-prompt "Select file" default)
files nil :require-match nil 'prot-org-file-history default))
(user-error "There are no files in the `org-directory'")))
;;;###autoload
(defun prot-org-select-project (file line-with-heading)
(interactive
(let ((f (prot-org-file-prompt)))
(list f (prot-org-outline-prompt f))))
(pcase-let* ((`(,line ,text) (split-string line-with-heading "\t"))
(line (string-to-number line)))
(with-current-buffer (find-file file)
(goto-char (point-min))
(forward-line (1- line))
(org-insert-subheading '(4)))))
;;;###autoload
(defun prot-org-capture-select-project ()
"Like `prot-org-select-project' but specifically for `org-capture'."
(declare (interactive-only t))
(interactive)
(call-interactively 'prot-org-select-project))
;;;; org-agenda
(declare-function calendar-day-name "calendar")

View file

@ -142,6 +142,87 @@ DEADLINE: %%^T
(advice-add 'org-capture-place-template :around 'prot-org--capture-no-delete-windows)
(advice-add 'org-add-log-note :around 'prot-org--capture-no-delete-windows)
;;;;; Custom function to select a project to add to
(defun prot-org--get-outline (&optional file)
"Return `outline-regexp' headings and line numbers of current file or FILE."
(with-current-buffer (find-file-noselect file)
(let ((outline-regexp (format "^\\(?:%s\\)" (or (bound-and-true-p outline-regexp) "[*\^L]+")))
candidates)
(save-excursion
(goto-char (point-min))
(while (if (bound-and-true-p outline-search-function)
(funcall outline-search-function)
(re-search-forward outline-regexp nil t))
(push
;; NOTE 2024-11-24: The -5 (minimum width) is a sufficiently high number to keep the
;; alignment consistent in most cases. Larger files will simply shift the heading text
;; in minibuffer, but this is not an issue anymore.
(format "%-5s\t%s"
(line-number-at-pos (point))
(buffer-substring-no-properties (line-beginning-position) (line-end-position)))
candidates)
(goto-char (1+ (line-end-position)))))
(if candidates
(nreverse candidates)
(user-error "No outline")))))
(defvar prot-org-outline-history nil
"Minibuffer history for `prot-org-outline-prompt'.")
(defun prot-org-outline-prompt (&optional file)
"Prompt for outline among headings retrieved by `prot-org--get-outline'.
With optional FILE use the outline of it, otherwise use that of
the current file."
(let ((current-file (or file buffer-file-name))
(default (car prot-org-outline-history)))
(completing-read
(format-prompt
(format "Select heading inside `%s': "
(propertize (file-name-nondirectory current-file) 'face 'error))
default)
(prot-common-completion-table-no-sort 'imenu (prot-org--get-outline current-file))
nil :require-match nil 'prot-org-outline-history default)))
(defvar prot-org-file-history nil
"Minibuffer history of `prot-org-file-prompt'.")
(defun prot-org--not-useful-p (file)
"Return non-nil if FILE is not a useful Org file for `org-capture'."
(or (string-match-p "\\.org_archive\\'" file)
(backup-file-name-p file)
(not (string-match-p "\\.org\\'" file))))
(defun prot-org-file-prompt ()
"Select a file in the `org-directory'."
(if-let ((dir org-directory)
(files (directory-files-recursively org-directory ".*" nil))
(files (seq-remove #'prot-org--not-useful-p files)))
(let ((default (car prot-org-file-history)))
(completing-read
(format-prompt "Select file" default)
files nil :require-match nil 'prot-org-file-history default))
(user-error "There are no files in the `org-directory'")))
;;;###autoload
(defun prot-org-select-project (file line-with-heading)
(interactive
(let ((f (prot-org-file-prompt)))
(list f (prot-org-outline-prompt f))))
(pcase-let* ((`(,line ,text) (split-string line-with-heading "\t"))
(line (string-to-number line)))
(with-current-buffer (find-file file)
(goto-char (point-min))
(forward-line (1- line))
(org-insert-todo-subheading '(4)))))
;;;###autoload
(defun prot-org-capture-select-project ()
"Like `prot-org-select-project' but specifically for `org-capture'."
(declare (interactive-only t))
(interactive)
(call-interactively 'prot-org-select-project))
;;;; org-agenda
(declare-function calendar-day-name "calendar")