feat(emacs): move review to org-roam

This commit is contained in:
Pavel Korytov 2021-12-19 22:10:27 +03:00
parent 40c5ef92a0
commit a3ac781355
2 changed files with 224 additions and 210 deletions

View file

@ -127,6 +127,8 @@
(setq evil-search-module 'evil-search)
(setq evil-split-window-below t)
(setq evil-vsplit-window-right t)
(unless (display-graphic-p)
(setq evil-want-C-i-jump nil))
:config
(evil-mode 1)
;; (setq evil-respect-visual-line-mode t)
@ -2318,7 +2320,7 @@ _r_: Restart frame _uo_: Output _sd_: Down stack frame _bh_: Set
(general-nmap :keymaps 'org-mode-map
"C-x C-l" 'my/org-link-copy)
(setq org-roam-directory (concat org-directory "/roam"))
(setq org-agenda-files '("inbox.org" "projects.org" "work.org" "sem-11.org" "life.org"))
(setq org-agenda-files '("inbox.org"))
;; (setq org-default-notes-file (concat org-directory "/notes.org"))
(add-to-list 'org-global-properties
'("Effort_ALL" . "0 0:05 0:10 0:15 0:30 0:45 1:00 2:00 4:00"))
@ -2738,13 +2740,32 @@ _r_: Restart frame _uo_: Output _sd_: Down stack frame _bh_: Set
(org-roam-setup)
(require 'org-roam-protocol))
(setq my/org-roam-project-template
`("p" "project" plain ,(string-join
'("%?"
"* Tasks"
"** TODO Add initials tasks"
"* Log :log_here:")
"\n")
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
,(string-join
'("#+title: ${title}"
"#+category: ${title}"
"#+filetags: :org:log_here:"
"#+TODO: TODO(t) NEXT(n) HOLD(h) | NO(q) DONE(d)"
"#+TODO: FUTURE(f) | PASSED(p)"
"#+STARTUP: logdone overview")
"\n"))
:unnarrowed t))
(setq org-roam-capture-templates
`(("d" "default" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
:unnarrowed t)
("e" "encrypted" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org.gpg" "#+title: ${title}\n")
:unnarrowed t)))
:unnarrowed t)
,my/org-roam-project-template))
(defun my/make-daily-header-track ()
(when (fboundp 'emms-playlist-current-selected-track)
@ -2834,32 +2855,7 @@ _r_: Restart frame _uo_: Output _sd_: Down stack frame _bh_: Set
nil
(my/org-roam-filter-by-tag :include ("org"))
:templates
`(("p" "project" plain ,(string-join
'("%?"
"* Tasks"
"** TODO Add initials tasks"
"* Log :log_here:")
"\n")
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
"#+title: ${title}\n#+category: ${title}\n#+filetags: :org:log_here:")
:unnarrowed t))))
(defun my/org-roam-capture-task ()
(interactive)
(org-roam-capture-
:node (org-roam-node-read
nil
(my/org-roam-filter-by-tag :include ("org")))
:templates
`(("p" "project" plain "** TODO %?"
:if-new (file+head+olp "%<%Y%m%d%H%M%S>-${slug}.org"
,(string-join
'("%?"
"* Tasks"
"** TODO Add initials tasks"
"* Log :log_here:")
"\n")
("Tasks"))))))
`(,my/org-roam-project-template)))
(defun my/org-roam-daily-extract-target-links ()
(save-excursion
@ -3103,59 +3099,65 @@ _r_: Restart frame _uo_: Output _sd_: Down stack frame _bh_: Set
(let ((default-directory org-directory))
(my/get-files-status (format "@{%s}" date))))
(defun my/org-review-format-roam (rev)
(let* ((changes (my/org-changed-files-since-date rev))
(new-roam
(seq-filter
(lambda (elem)
(and (eq (car elem) 'added)
(string-match-p (rx bos "roam") (cdr elem))))
changes))
(changed-roam
(seq-filter
(lambda (elem)
(and (eq (car elem) 'modified)
(string-match-p (rx bos "roam") (cdr elem))))
changes)))
(concat
(unless (seq-empty-p new-roam)
(concat "** New Roam entries \n"
(mapconcat
(lambda (entry)
(format "- [[file:%s][%s]]" (cdr entry) (cdr entry)))
new-roam
"\n")
"\n"))
(unless (seq-empty-p changed-roam)
(concat "** Changed Roam entries \n"
(mapconcat
(lambda (entry)
(format "- [[file:%s][%s]]" (cdr entry) (cdr entry)))
changed-roam
"\n"))))))
(setq my/org-review-roam-queries
'((:status added
:tags (:include ("org"))
:title "New Project Entries")
(:status changed
:tags (:include ("org"))
:title "Changed Project Entries")
(:status added
:tags (:include ("log") :exclude ("org" "log_here"))
:title "New Dailies")
(:status added
:tags (:exclude ("log" "org"))
:title "New Zettelkasten Entries")
(:status changed
:tags (:exclude ("log" "org"))
:title "Changed Zettelkasten Entries")))
(defun my/org-journal-entries-since-date (rev-date)
(mapcar
(lambda (date)
(let ((time (encode-time (parse-time-string date))))
`((file . ,(org-journal--get-entry-path time))
(header . ,(format-time-string org-journal-date-format time)))))
(seq-filter
(lambda (date) (string-lessp rev-date date))
(mapcar
(lambda (date)
(format "%04d-%02d-%02dT00:00:00+0300" (nth 2 date) (nth 0 date) (nth 1 date)))
(org-journal--list-dates)))))
(defun my/org-review-format-journal (rev-date)
(mapconcat
(lambda (item)
(format "- [[file:%s::*%s][%s]]"
(cdr (assoc 'file item))
(cdr (assoc 'header item))
(cdr (assoc 'header item))))
(my/org-journal-entries-since-date rev-date)
"\n"))
(defun my/org-review-format-roam (changes)
(cl-loop for query in my/org-review-roam-queries
with nodes = (org-roam-node-list)
with node-tags = (mapcar #'org-roam-node-tags nodes)
for include-tags = (plist-get (plist-get query :tags) :include)
for exclude-tags = (plist-get (plist-get query :tags) :exclude)
;; List of nodes filtered by :tags in query
for filtered-nodes =
(cl-loop for node in nodes
for tags in node-tags
if (and
(or (seq-empty-p include-tags)
(seq-intersection include-tags tags))
(or (seq-empty-p exclude-tags)
(not (seq-intersection exclude-tags tags))))
collect node)
;; List of changes filtered by :status in query
for filtered-changes =
(cl-loop for change in changes
if (and (eq (car change) (plist-get query :status))
(string-match-p (rx bos "roam") (cdr change)))
collect (cdr change))
;; Intersection of the two filtered lists
for final-nodes =
(cl-loop for node in filtered-nodes
for path = (file-relative-name (org-roam-node-file node)
org-directory)
if (member path filtered-changes)
collect node)
;; If the intersction list is not empty, format it to the result
if final-nodes
concat (format "** %s\n" (plist-get query :title))
;; FInal list of links, sorted by title
and concat (cl-loop for node in (seq-sort
(lambda (node1 node2)
(string-lessp
(org-roam-node-title node1)
(org-roam-node-title node2)))
final-nodes)
concat (format "- [[id:%s][%s]]\n"
(org-roam-node-id node)
(org-roam-node-title node)))))
(setq my/org-ql-review-queries
`(("Waitlist" scheduled scheduled
@ -3219,9 +3221,6 @@ _r_: Restart frame _uo_: Output _sd_: Down stack frame _bh_: Set
(setq my/org-review-directory "review")
(defun my/org-review-get-filename ()
(concat my/org-review-directory "/" (format-time-string "%Y-%m-%d.org" (current-time))))
(defun my/get-last-review-date ()
(substring
(or
@ -3229,33 +3228,37 @@ _r_: Restart frame _uo_: Output _sd_: Down stack frame _bh_: Set
'string-greaterp
(-filter
(lambda (f) (not (or (string-equal f ".") (string-equal f ".."))))
(directory-files (f-join org-directory my/org-review-directory))))
(directory-files (f-join org-roam-directory my/org-review-directory))))
(format-time-string
"%Y-%m-%d"
(time-subtract
(current-time)
(seconds-to-time (* 60 60 24 7)))))
(seconds-to-time (* 60 60 24 14)))))
0 10))
(setq my/org-review-capture-template
`("r" "Review" plain (file ,(my/org-review-get-filename))
`("r" "Review" plain
,(string-join
'("#+TITLE: Review %t"
'("#+title: %<%Y-%m-%d>: REVIEW"
"#+category: REVIEW"
"#+filetags: log review"
"#+STARTUP: overview"
""
"Last review date: %(org-timestamp-translate (org-timestamp-from-string (format \"<%s>\" (my/get-last-review-date))))"
""
"* Roam"
"%(my/org-review-format-roam (my/get-last-review-date))"
"* Journal"
"New journal entries:"
"%(my/org-review-format-journal (my/get-last-review-date))"
"%(my/org-review-format-roam (my/org-changed-files-since-date (my/get-last-review-date)))"
"* Agenda"
"%(my/org-review-format-queries (my/get-last-review-date))"
"* Thoughts :crypt:"
"* Thoughts"
"%?")
"\n")))
"\n")
:if-new (file "review/%<%Y-%m-%d>.org.gpg")))
(add-to-list 'org-capture-templates my/org-review-capture-template t)
(defun my/org-roam-capture-review ()
(interactive)
(org-roam-capture- :node (org-roam-node-create)
:templates `(,my/org-review-capture-template)))
(use-package org-ref
:straight (:files (:defaults (:exclude "*helm*")))
@ -4032,8 +4035,11 @@ _r_: Restart frame _uo_: Output _sd_: Down stack frame _bh_: Set
(add-hook 'emms-playlist-cleared-hook 'emms-player-mpd-clear)
(emms-player-set emms-player-mpd
'regex
(emms-player-simple-regexp
"m3u" "ogg" "flac" "mp3" "wav" "mod" "au" "aiff"))
(rx (or (: "https://" (* nonl) (or "acast.com") (* nonl))
(+ (? (or "https://" "http://"))
(* nonl)
(regexp (eval (emms-player-simple-regexp
"m3u" "ogg" "flac" "mp3" "wav" "mod" "au" "aiff")))))))
;; MPV setup
(add-to-list 'emms-player-list 'emms-player-mpv)
(emms-player-set emms-player-mpv

236
Emacs.org
View file

@ -590,6 +590,8 @@ Basic evil configuration.
(setq evil-search-module 'evil-search)
(setq evil-split-window-below t)
(setq evil-vsplit-window-right t)
(unless (display-graphic-p)
(setq evil-want-C-i-jump nil))
:config
(evil-mode 1)
;; (setq evil-respect-visual-line-mode t)
@ -3788,7 +3790,7 @@ Some inspiration:
Used files
#+begin_src emacs-lisp :tangle no :noweb-ref org-productivity-setup
(setq org-roam-directory (concat org-directory "/roam"))
(setq org-agenda-files '("inbox.org" "projects.org" "work.org" "sem-11.org" "life.org"))
(setq org-agenda-files '("inbox.org"))
;; (setq org-default-notes-file (concat org-directory "/notes.org"))
#+end_src
@ -4019,13 +4021,32 @@ References:
Capture templates for =org-roam-capture=. As for now, nothing too complicated here.
#+begin_src emacs-lisp
(setq my/org-roam-project-template
`("p" "project" plain ,(string-join
'("%?"
"* Tasks"
"** TODO Add initials tasks"
"* Log :log_here:")
"\n")
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
,(string-join
'("#+title: ${title}"
"#+category: ${title}"
"#+filetags: :org:log_here:"
"#+TODO: TODO(t) NEXT(n) HOLD(h) | NO(q) DONE(d)"
"#+TODO: FUTURE(f) | PASSED(p)"
"#+STARTUP: logdone overview")
"\n"))
:unnarrowed t))
(setq org-roam-capture-templates
`(("d" "default" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
:unnarrowed t)
("e" "encrypted" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org.gpg" "#+title: ${title}\n")
:unnarrowed t)))
:unnarrowed t)
,my/org-roam-project-template))
#+end_src
**** Org Roam Dailies
=org-roam-dailies= provides journaling capabilities similar to [[https://github.com/bastibe/org-journal][Org Journal]]. I was using the latter for half a year or so but decided to try to use Org Roam for this purpose.
@ -4150,35 +4171,7 @@ Find or capture a project.
nil
(my/org-roam-filter-by-tag :include ("org"))
:templates
`(("p" "project" plain ,(string-join
'("%?"
"* Tasks"
"** TODO Add initials tasks"
"* Log :log_here:")
"\n")
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
"#+title: ${title}\n#+category: ${title}\n#+filetags: :org:log_here:")
:unnarrowed t))))
#+end_src
Find or capture a project task. I'm not sure if I'll be using it, as usually capture tasks to inbox and then refile them to where they belong, but I'll leave the function here for now.
#+begin_src emacs-lisp
(defun my/org-roam-capture-task ()
(interactive)
(org-roam-capture-
:node (org-roam-node-read
nil
(my/org-roam-filter-by-tag :include ("org")))
:templates
`(("p" "project" plain "** TODO %?"
:if-new (file+head+olp "%<%Y%m%d%H%M%S>-${slug}.org"
,(string-join
'("%?"
"* Tasks"
"** TODO Add initials tasks"
"* Log :log_here:")
"\n")
("Tasks"))))))
`(,my/org-roam-project-template)))
#+end_src
**** Automatic transclusion for Dailies
I've been using org-journal for quite some time, and while it's great, I don't like its linear structure too much. I have all kinds of stuff written there - things related to my job, various personal projects, etc, and it's pretty hard to query a specific thing.
@ -4385,7 +4378,7 @@ A set of keybindings to quickly access things in Org Roam.
As of now, I have 3 categories of nodes in Org Roam:
- dailies (tag =log=)
- project notes (tag =org=)
- Zettlekasten, which is everything else.
- Zettelkasten, which is everything else.
So I want to have a separate fuzzy search for every category. =my/org-roam-find-project= for project nodes is already defined above, here is the rest:
#+begin_src emacs-lisp
@ -4475,15 +4468,15 @@ Don't forget to run the following after setup:
xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
#+end_src
*** Review workflow
| Type | Note |
|------+----------------------------------|
| TODO | Update this for org-roam dailies |
My take on a review workflow. As a baseline, I want to have a template that lists the important changes since the last review and other basic information. I'm doing reviews regularly, but the time intervals still may vary, hence this flexibility.
**** Data from git & org-roam
This section has seen some updates over time.
**** Data from git
First, as I have [[file:Console.org::=autocommit=][autocommit]] set up in my org directory, here is a handy function to get an alist of changed files of a form =(status . path)=. In principle, the =rev= parameter can be a commit, tag, etc but here I'm interested in a form like =@{2021-08-30}=.
Also in principle, Org Roam DB also stores stuff like creation time and modification time, but I started this section before I started using Org Roam extensively, so git works fine for me.
#+begin_src emacs-lisp
(setq my/git-diff-status
'(("A" . added)
@ -4505,78 +4498,89 @@ First, as I have [[file:Console.org::=autocommit=][autocommit]] set up in my org
(split-string files "\n" t))))
#+end_src
I'll use it to get a list of added and changed Roam files since the last review. The date should be in a format =YYYY-MM-DD=.
I'll use it to get a list of added and changed files in the Org directory since the last review. The date should be in a format =YYYY-MM-DD=.
#+begin_src emacs-lisp
(defun my/org-changed-files-since-date (date)
(let ((default-directory org-directory))
(my/get-files-status (format "@{%s}" date))))
#+end_src
**** Data from org-roam
Now that we have the list of new & changed files, I want to sort into a bunch of categories: projects, log entries, etc. The categories are defined by tags.
Now we are ready to format this list to insert it into the capture template.
So here is a list of plists that sets these categories. The properties are as follows:
- =:status= is a git status for the file
- =:tags= is a plist that sets up the following conditions for the Roam node
- =:include= - should be empty or one of these should be present
- =:exclude= - should be empty or none of these should be present
- =:title= is the name of category as I want it to be seen in the review template
#+begin_src emacs-lisp
(defun my/org-review-format-roam (rev)
(let* ((changes (my/org-changed-files-since-date rev))
(new-roam
(seq-filter
(lambda (elem)
(and (eq (car elem) 'added)
(string-match-p (rx bos "roam") (cdr elem))))
changes))
(changed-roam
(seq-filter
(lambda (elem)
(and (eq (car elem) 'modified)
(string-match-p (rx bos "roam") (cdr elem))))
changes)))
(concat
(unless (seq-empty-p new-roam)
(concat "** New Roam entries \n"
(mapconcat
(lambda (entry)
(format "- [[file:%s][%s]]" (cdr entry) (cdr entry)))
new-roam
"\n")
"\n"))
(unless (seq-empty-p changed-roam)
(concat "** Changed Roam entries \n"
(mapconcat
(lambda (entry)
(format "- [[file:%s][%s]]" (cdr entry) (cdr entry)))
changed-roam
"\n"))))))
#+end_src
**** Data from org-journal
Second, I want to have a list of new journal entries since the last review.
#+begin_src emacs-lisp
(defun my/org-journal-entries-since-date (rev-date)
(mapcar
(lambda (date)
(let ((time (encode-time (parse-time-string date))))
`((file . ,(org-journal--get-entry-path time))
(header . ,(format-time-string org-journal-date-format time)))))
(seq-filter
(lambda (date) (string-lessp rev-date date))
(mapcar
(lambda (date)
(format "%04d-%02d-%02dT00:00:00+0300" (nth 2 date) (nth 0 date) (nth 1 date)))
(org-journal--list-dates)))))
(setq my/org-review-roam-queries
'((:status added
:tags (:include ("org"))
:title "New Project Entries")
(:status changed
:tags (:include ("org"))
:title "Changed Project Entries")
(:status added
:tags (:include ("log") :exclude ("org" "log_here"))
:title "New Dailies")
(:status added
:tags (:exclude ("log" "org"))
:title "New Zettelkasten Entries")
(:status changed
:tags (:exclude ("log" "org"))
:title "Changed Zettelkasten Entries")))
#+end_src
Format the results:
This list is used to extract & format the relevant section of the review template.
=cl-loop= seems pretty good as a control flow structure, but I'll see if it is also pretty good at producing poorly maintainable code. At least at the moment of this writing, the function below looks rather concise.
#+begin_src emacs-lisp
(defun my/org-review-format-journal (rev-date)
(mapconcat
(lambda (item)
(format "- [[file:%s::*%s][%s]]"
(cdr (assoc 'file item))
(cdr (assoc 'header item))
(cdr (assoc 'header item))))
(my/org-journal-entries-since-date rev-date)
"\n"))
(defun my/org-review-format-roam (changes)
(cl-loop for query in my/org-review-roam-queries
with nodes = (org-roam-node-list)
with node-tags = (mapcar #'org-roam-node-tags nodes)
for include-tags = (plist-get (plist-get query :tags) :include)
for exclude-tags = (plist-get (plist-get query :tags) :exclude)
;; List of nodes filtered by :tags in query
for filtered-nodes =
(cl-loop for node in nodes
for tags in node-tags
if (and
(or (seq-empty-p include-tags)
(seq-intersection include-tags tags))
(or (seq-empty-p exclude-tags)
(not (seq-intersection exclude-tags tags))))
collect node)
;; List of changes filtered by :status in query
for filtered-changes =
(cl-loop for change in changes
if (and (eq (car change) (plist-get query :status))
(string-match-p (rx bos "roam") (cdr change)))
collect (cdr change))
;; Intersection of the two filtered lists
for final-nodes =
(cl-loop for node in filtered-nodes
for path = (file-relative-name (org-roam-node-file node)
org-directory)
if (member path filtered-changes)
collect node)
;; If the intersction list is not empty, format it to the result
if final-nodes
concat (format "** %s\n" (plist-get query :title))
;; FInal list of links, sorted by title
and concat (cl-loop for node in (seq-sort
(lambda (node1 node2)
(string-lessp
(org-roam-node-title node1)
(org-roam-node-title node2)))
final-nodes)
concat (format "- [[id:%s][%s]]\n"
(org-roam-node-id node)
(org-roam-node-title node)))))
#+end_src
**** Data from org-agenda via org-ql
Third, I want to list some changes in my agenda. This section will change depending on what I'm currently working on.
+Third+ second, I want to list some changes in my agenda. This section will change depending on what I'm currently working on.
So, here is a list of queries results of which I want to see in the review template. The format is =(name date-field order-by-field query)=.
#+begin_src emacs-lisp
@ -4652,16 +4656,13 @@ Execute all the saved queries and format an Org list for the capture template.
**** Capture template
Now, we have to put all this together and define a capture template for the review.
I'll use a separate directory for the review files, just like for org-journal and org-roam. The filename will have a format =YYYY-MM-DD.org=, which will also free me from the effort of storing the last review date somewhere.
+I'll use a separate directory for the review files, just like for org-journal and org-roam.+ I'll store the review files in org-roam. Time will tell if that's a good idea. The filename will have a format =YYYY-MM-DD.org=, which will also free me from the effort of storing the last review date somewhere.
If somehow there are no files in the folder, fallback to the current date minus one week.
#+begin_src emacs-lisp
(setq my/org-review-directory "review")
(defun my/org-review-get-filename ()
(concat my/org-review-directory "/" (format-time-string "%Y-%m-%d.org" (current-time))))
(defun my/get-last-review-date ()
(substring
(or
@ -4669,36 +4670,40 @@ If somehow there are no files in the folder, fallback to the current date minus
'string-greaterp
(-filter
(lambda (f) (not (or (string-equal f ".") (string-equal f ".."))))
(directory-files (f-join org-directory my/org-review-directory))))
(directory-files (f-join org-roam-directory my/org-review-directory))))
(format-time-string
"%Y-%m-%d"
(time-subtract
(current-time)
(seconds-to-time (* 60 60 24 7)))))
(seconds-to-time (* 60 60 24 14)))))
0 10))
#+end_src
A template looks like this:
#+begin_src emacs-lisp
(setq my/org-review-capture-template
`("r" "Review" plain (file ,(my/org-review-get-filename))
`("r" "Review" plain
,(string-join
'("#+TITLE: Review %t"
'("#+title: %<%Y-%m-%d>: REVIEW"
"#+category: REVIEW"
"#+filetags: log review"
"#+STARTUP: overview"
""
"Last review date: %(org-timestamp-translate (org-timestamp-from-string (format \"<%s>\" (my/get-last-review-date))))"
""
"* Roam"
"%(my/org-review-format-roam (my/get-last-review-date))"
"* Journal"
"New journal entries:"
"%(my/org-review-format-journal (my/get-last-review-date))"
"%(my/org-review-format-roam (my/org-changed-files-since-date (my/get-last-review-date)))"
"* Agenda"
"%(my/org-review-format-queries (my/get-last-review-date))"
"* Thoughts :crypt:"
"* Thoughts"
"%?")
"\n")))
"\n")
:if-new (file "review/%<%Y-%m-%d>.org.gpg")))
(add-to-list 'org-capture-templates my/org-review-capture-template t)
(defun my/org-roam-capture-review ()
(interactive)
(org-roam-capture- :node (org-roam-node-create)
:templates `(,my/org-review-capture-template)))
#+end_src
*** org-ref
| Type | Description |
@ -5891,8 +5896,11 @@ Set a custom regex for MPD. EMMS sets up the default one from MPD's diagnostic o
#+begin_src emacs-lisp
(emms-player-set emms-player-mpd
'regex
(emms-player-simple-regexp
"m3u" "ogg" "flac" "mp3" "wav" "mod" "au" "aiff"))
(rx (or (: "https://" (* nonl) (or "acast.com") (* nonl))
(+ (? (or "https://" "http://"))
(* nonl)
(regexp (eval (emms-player-simple-regexp
"m3u" "ogg" "flac" "mp3" "wav" "mod" "au" "aiff")))))))
#+end_src
After all this is done, run =M-x emms-cache-set-from-mpd-all= to set cache from MPD. If everything is correct, EMMS browser will be populated with MPD database.