blob: b8312877958feaed4d1d79a7d16519a36cc981c8 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
;;; org-refile-config.el --- Org Refile Customizations -*- lexical-binding: t; coding: utf-8; -*-
;; author: Craig Jennings <c@cjennings.net>
;;; Commentary:
;; Configuration and custom functions for org-mode refiling.
;;
;; Performance:
;; - Caches refile targets to avoid scanning 34,000+ files on every refile
;; - Cache builds asynchronously 5 seconds after Emacs startup (non-blocking)
;; - First refile uses cache if ready, otherwise builds synchronously (one-time delay)
;; - Subsequent refiles are instant (cached)
;; - Cache auto-refreshes after 1 hour
;; - Manual refresh: M-x cj/org-refile-refresh-targets (e.g., after adding projects)
;;; Code:
;; ----------------------------- Org Refile Targets ----------------------------
;; sets refile targets
;; - adds project files in org-roam to the refile targets
;; - adds todo.org files in subdirectories of the code and project directories
(defvar cj/org-refile-targets-cache nil
"Cached refile targets to avoid expensive directory scanning.
Set to nil to invalidate cache.")
(defvar cj/org-refile-targets-cache-time nil
"Time when refile targets cache was last built.")
(defvar cj/org-refile-targets-cache-ttl 3600
"Time-to-live for refile targets cache in seconds (default: 1 hour).")
(defvar cj/org-refile-targets-building nil
"Non-nil when refile targets are being built asynchronously.
Prevents duplicate builds if user refiles before async build completes.")
(defun cj/build-org-refile-targets (&optional force-rebuild)
"Build =org-refile-targets= with caching.
When FORCE-REBUILD is non-nil, bypass cache and rebuild from scratch.
Otherwise, returns cached targets if available and not expired.
This function scans 30,000+ files across code/projects directories,
so caching improves performance from 15-20 seconds to instant."
(interactive "P")
;; Check if we can use cache
(let ((cache-valid (and cj/org-refile-targets-cache
cj/org-refile-targets-cache-time
(not force-rebuild)
(< (- (float-time) cj/org-refile-targets-cache-time)
cj/org-refile-targets-cache-ttl))))
(if cache-valid
;; Use cached targets (instant)
(progn
(setq org-refile-targets cj/org-refile-targets-cache)
(when (called-interactively-p 'interactive)
(message "Using cached refile targets (%d files)"
(length org-refile-targets))))
;; Check if async build is in progress
(when cj/org-refile-targets-building
(message "Waiting for background cache build to complete..."))
;; Rebuild from scratch (slow - scans 34,000+ files)
(unwind-protect
(progn
(setq cj/org-refile-targets-building t)
(let ((start-time (current-time))
(new-files
(list
(cons inbox-file '(:maxlevel . 1))
(cons reference-file '(:maxlevel . 2))
(cons schedule-file '(:maxlevel . 1)))))
;; Extend with org-roam files if available AND org-roam is loaded
(when (and (fboundp 'cj/org-roam-list-notes-by-tag)
(fboundp 'org-roam-node-list))
(let* ((project-and-topic-files
(append (cj/org-roam-list-notes-by-tag "Project")
(cj/org-roam-list-notes-by-tag "Topic")))
(file-rule '(:maxlevel . 1)))
(dolist (file project-and-topic-files)
(unless (assoc file new-files)
(push (cons file file-rule) new-files)))))
;; Add todo.org files from known directories
(dolist (dir (list user-emacs-directory code-dir projects-dir))
(let* ((todo-files (directory-files-recursively
dir "^[Tt][Oo][Dd][Oo]\\.[Oo][Rr][Gg]$"))
(file-rule '(:maxlevel . 1)))
(dolist (file todo-files)
(unless (assoc file new-files)
(push (cons file file-rule) new-files)))))
;; Update targets and cache
(setq new-files (nreverse new-files))
(setq org-refile-targets new-files)
(setq cj/org-refile-targets-cache new-files)
(setq cj/org-refile-targets-cache-time (float-time))
(when (called-interactively-p 'interactive)
(message "Built refile targets: %d files in %.2f seconds"
(length org-refile-targets)
(float-time-since start-time)))))
;; Always clear the building flag, even if build fails
(setq cj/org-refile-targets-building nil)))))
;; Build cache asynchronously after startup to avoid blocking Emacs
(run-with-idle-timer
5 ; Wait 5 seconds after Emacs is idle
nil ; Don't repeat
(lambda ()
(message "Building org-refile targets cache in background...")
(cj/build-org-refile-targets)))
(defun cj/org-refile-refresh-targets ()
"Force rebuild of refile targets cache.
Use this after adding new projects or todo.org files.
Bypasses cache and scans all directories from scratch."
(interactive)
(cj/build-org-refile-targets 'force-rebuild))
(defun cj/org-refile (&optional ARG DEFAULT-BUFFER RFLOC MSG)
"Call org-refile with cached refile targets.
Uses cached targets for performance (instant vs 15-20 seconds).
Cache auto-refreshes after 1 hour or on Emacs restart.
To manually refresh cache (e.g., after adding projects):
M-x cj/org-refile-refresh-targets
ARG DEFAULT-BUFFER RFLOC and MSG parameters passed to org-refile."
(interactive "P")
;; Use cached targets (don't rebuild every time!)
(cj/build-org-refile-targets)
(org-refile ARG DEFAULT-BUFFER RFLOC MSG))
;; ----------------------------- Org Refile In File ----------------------------
;; convenience function for scoping the refile candidates to the current buffer.
(defun cj/org-refile-in-file ()
"Refile to a target within the current file and save the buffer."
(interactive)
(let ((org-refile-targets `(((,(buffer-file-name)) :maxlevel . 6))))
(call-interactively 'org-refile)
(save-buffer)))
;; --------------------------------- Org Refile --------------------------------
(use-package org-refile
:ensure nil ;; built-in
:defer .5
:bind
(:map org-mode-map
("C-c C-w" . cj/org-refile)
("C-c w" . cj/org-refile-in-file))
:config
;; save all open org buffers after a refile is complete
(advice-add 'org-refile :after
(lambda (&rest _)
(org-save-all-org-buffers))))
(provide 'org-refile-config)
;;; org-refile-config.el ends here.
|