From dd0060b8c0cd982de4fd88f03f0090e482e857ee Mon Sep 17 00:00:00 2001 From: Marc Date: Mon, 30 Jan 2023 14:53:43 +0100 Subject: [PATCH] helper functions to automate agenda files update for org-roam files --- config.org | 143 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 11 deletions(-) diff --git a/config.org b/config.org index 7f9be98..ceafdef 100644 --- a/config.org +++ b/config.org @@ -818,7 +818,17 @@ If the property is already set, replace its value." ; (add-hook 'org-mode-hook 'org-indent-mode) :bind (:map org-mode-map ("S-" . org-shiftright) ("S-" . org-shiftleft)) - + :init + (defun my--org-agenda-files-set () + "Sets default agenda files. +Necessary when updating roam agenda todos." + (setq org-agenda-files (list (concat MY--PATH_ORG_FILES "notes.org") + (concat MY--PATH_ORG_FILES "projects.org") + (concat MY--PATH_ORG_FILES "tasks.org"))) + (when *sys/linux* + (nconc org-agenda-files + (directory-files-recursively MY--PATH_ORG_FILES_MOBILE "\\.org$")))) + (my--org-agenda-files-set) :config (defun my--org-company () (set (make-local-variable 'company-backends) @@ -829,14 +839,7 @@ If the property is already set, replace its value." org-habit org-tempo ;; easy templates ))) - (setq org-default-notes-file (concat MY--PATH_ORG_FILES "notes.org") - org-agenda-files (list (concat MY--PATH_ORG_FILES "notes.org") - (concat MY--PATH_ORG_FILES "projects.org") - (concat MY--PATH_ORG_FILES "tasks.org"))) - - (when *sys/linux* - (nconc org-agenda-files - (directory-files-recursively MY--PATH_ORG_FILES_MOBILE "\\.org$"))) + (setq org-default-notes-file (concat MY--PATH_ORG_FILES "notes.org")) (setq org-id-locations-file (concat MY--PATH_USER_LOCAL ".org-id-locations") org-log-into-drawer "LOGBOOK") @@ -1066,7 +1069,118 @@ org-roam-db-sync :after org :init (setq org-roam-v2-ack t) + + (defun my--project-p () + "Return non-nil if current buffer has any todo entry. + +TODO entries marked as done are ignored, meaning this function +returns nil if current buffer contains only completed tasks." + (seq-find + (lambda (type) + (eq type 'todo)) + (org-element-map + (org-element-parse-buffer 'headline) + 'headline + (lambda (h) + (org-element-property :todo-type h))))) + + (defun my--project-update-tag () + "Update PROJECT tag in the current buffer." + (when (and (not (active-minibuffer-window)) + (my--buffer-roam-note-p)) + (save-excursion + (goto-char (point-min)) + (let* ((tags (my--buffer-tags-get)) + (original-tags tags)) + (if (my--project-p) + (setq tags (cons "project" tags)) + (setq tags (remove "project" tags))) + + ;;cleanup duplicates + (when (or (seq-difference tags original-tags) + (seq-difference original-tags tags)) + (apply #'my--buffer-tags-set tags)))))) + + (defun my--buffer-tags-get () + "Return filetags value in current buffer." + (my--buffer-prop-get-list "filetags" "[ :]")) + + (defun my--buffer-tags-set (&rest tags) + "Set TAGS in current buffer. +If filetags value is already set, replace it." + (if tags + (my--buffer-prop-set + "filetags" (concat ":" (string-join tags ":") ":")) + (my--buffer-prop-remove "filetags"))) + + (defun my--buffer-tags-add (tag) + "Add a TAG to filetags in current buffer." + (let* ((tags (my--buffer-tags-get)) + (tags (append tags (list tag)))) + (apply #'my--buffer-tags-set tags))) + + (defun my--buffer-tags-remove (tag) + "Remove a TAG from filetags in current buffer." + (let* ((tags (my--buffer-tags-get)) + (tags (delete tag tags))) + (apply #'my--buffer-tags-set tags))) + + (defun my--buffer-prop-set (name value) + "Set a file property called NAME to VALUE in buffer file. +If the property is already set, replace its value." + (setq name (downcase name)) + (org-with-point-at 1 + (let ((case-fold-search t)) + (if (re-search-forward (concat "^#\\+" name ":\\(.*\\)") + (point-max) t) + (replace-match (concat "#+" name ": " value) 'fixedcase) + (while (and (not (eobp)) + (looking-at "^[#:]")) + (if (save-excursion (end-of-line) (eobp)) + (progn + (end-of-line) + (insert "\n")) + (forward-line) + (beginning-of-line))) + (insert "#+" name ": " value "\n"))))) + (defun my--buffer-prop-set-list (name values &optional separators) + "Set a file property called NAME to VALUES in current buffer. +VALUES are quoted and combined into single string using +`combine-and-quote-strings'. +If SEPARATORS is non-nil, it should be a regular expression +matching text that separates, but is not part of, the substrings. +If nil it defaults to `split-string-and-unquote', normally +\"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t. +If the property is already set, replace its value." + (my--buffer-prop-set + name (combine-and-quote-strings values separators))) + + (defun my--buffer-prop-get (name) + "Get a buffer property called NAME as a string." + (org-with-point-at 1 + (when (re-search-forward (concat "^#\\+" name ": \\(.*\\)") + (point-max) t) + (buffer-substring-no-properties + (match-beginning 1) + (match-end 1))))) + + (defun my--buffer-prop-get-list (name &optional separators) + "Get a buffer property NAME as a list using SEPARATORS. +If SEPARATORS is non-nil, it should be a regular expression +matching text that separates, but is not part of, the substrings. +If nil it defaults to `split-string-default-separators', normally +\"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t." + (let ((value (my--buffer-prop-get name))) + (when (and value (not (string-empty-p value))) + (split-string-and-unquote value separators)))) + (defun my--buffer-prop-remove (name) + "Remove a buffer property called NAME." + (org-with-point-at 1 + (when (re-search-forward (concat "\\(^#\\+" name ":.*\n?\\)") + (point-max) t) + (replace-match "")))) + (defun my--buffer-roam-note-p () "Return non-nil if the currently visited buffer is a note." (and buffer-file-name @@ -1085,11 +1199,18 @@ org-roam-db-sync (org-roam-node-list)))) (defun my/org-roam-refresh-agenda-list () - "Add all org roam files with #+filetags: Project" + "Add all org roam files with #+filetags: project" (interactive) + (my--org-agenda-files-set) (nconc org-agenda-files - (my--org-roam-list-notes-by-tag "Project")) + (my--org-roam-list-notes-by-tag "project")) (setq org-agenda-files (delete-dups org-agenda-files))) + + (add-hook 'find-file-hook #'my--project-update-tag) + (add-hook 'before-save-hook #'my--project-update-tag) + + (advice-add 'org-agenda :before #'my/org-roam-refresh-agenda-list) + (advice-add 'org-todo-list :before #'my/org-roam-refresh-agenda-list) :config (require 'org-roam-dailies) ;; ensure the keymap is available (org-roam-db-autosync-mode)