Skip to content

Commit a9bdfd7

Browse files
authored
Merge pull request #887 from multiplex55/codex/add-cached-index-path-for-todo-links
Cache `todo links` index by todo/note version signals
2 parents 136fa89 + 63f25ab commit a9bdfd7

1 file changed

Lines changed: 109 additions & 4 deletions

File tree

src/plugins/todo.rs

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ use crate::common::json_watch::{watch_json, JsonWatcher};
1212
use crate::common::lru::LruCache;
1313
use crate::common::query::parse_query_filters;
1414
use crate::linking::{
15-
build_index_from_notes_and_todos, format_link_id, EntityKey, LinkRef, LinkTarget,
15+
build_index_from_notes_and_todos, format_link_id, EntityKey, LinkIndex, LinkRef, LinkTarget,
1616
};
1717
use crate::plugin::Plugin;
18-
use crate::plugins::note::load_notes;
18+
use crate::plugins::note::{load_notes, note_version, Note};
1919
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
2020
use base64::Engine;
2121
use fuzzy_matcher::skim::SkimMatcherV2;
@@ -206,12 +206,70 @@ pub static TODO_DATA: Lazy<Arc<RwLock<Vec<TodoEntry>>>> =
206206
static TODO_CACHE: Lazy<Arc<RwLock<LruCache<String, Vec<Action>>>>> =
207207
Lazy::new(|| Arc::new(RwLock::new(LruCache::new(64))));
208208

209+
#[derive(Clone)]
210+
struct TodoLinksIndexCache {
211+
todo_version: u64,
212+
note_version: u64,
213+
notes: Vec<Note>,
214+
index: LinkIndex,
215+
}
216+
217+
static TODO_LINKS_INDEX_CACHE: Lazy<RwLock<Option<TodoLinksIndexCache>>> =
218+
Lazy::new(|| RwLock::new(None));
219+
220+
static TODO_LINKS_INDEX_REBUILD_COUNT: AtomicU64 = AtomicU64::new(0);
221+
209222
fn invalidate_todo_cache() {
210223
if let Ok(mut cache) = TODO_CACHE.write() {
211224
cache.clear();
212225
}
213226
}
214227

228+
fn invalidate_todo_links_index_cache() {
229+
if let Ok(mut cache) = TODO_LINKS_INDEX_CACHE.write() {
230+
*cache = None;
231+
}
232+
}
233+
234+
fn get_todo_links_index(notes_todos: &[TodoEntry]) -> (Vec<Note>, LinkIndex) {
235+
let todo_ver = todo_version();
236+
let note_ver = note_version();
237+
if let Ok(cache) = TODO_LINKS_INDEX_CACHE.read() {
238+
if let Some(entry) = cache.as_ref() {
239+
if entry.todo_version == todo_ver && entry.note_version == note_ver {
240+
return (entry.notes.clone(), entry.index.clone());
241+
}
242+
}
243+
}
244+
245+
let notes = load_notes().unwrap_or_default();
246+
let todos = notes_todos.to_vec();
247+
let index = build_index_from_notes_and_todos(&notes, &todos);
248+
TODO_LINKS_INDEX_REBUILD_COUNT.fetch_add(1, Ordering::SeqCst);
249+
250+
if let Ok(mut cache) = TODO_LINKS_INDEX_CACHE.write() {
251+
*cache = Some(TodoLinksIndexCache {
252+
todo_version: todo_ver,
253+
note_version: note_ver,
254+
notes: notes.clone(),
255+
index: index.clone(),
256+
});
257+
}
258+
259+
(notes, index)
260+
}
261+
262+
#[cfg(test)]
263+
fn reset_todo_links_index_cache_state() {
264+
invalidate_todo_links_index_cache();
265+
TODO_LINKS_INDEX_REBUILD_COUNT.store(0, Ordering::SeqCst);
266+
}
267+
268+
#[cfg(test)]
269+
fn todo_links_index_rebuild_count() -> u64 {
270+
TODO_LINKS_INDEX_REBUILD_COUNT.load(Ordering::SeqCst)
271+
}
272+
215273
fn bump_todo_version() {
216274
TODO_VERSION.fetch_add(1, Ordering::SeqCst);
217275
}
@@ -257,6 +315,7 @@ fn update_cache(list: Vec<TodoEntry>) {
257315
*lock = list;
258316
}
259317
invalidate_todo_cache();
318+
invalidate_todo_links_index_cache();
260319
bump_todo_version();
261320
}
262321

@@ -364,6 +423,7 @@ impl TodoPlugin {
364423
if let Ok(mut c) = cache_clone.write() {
365424
c.clear();
366425
}
426+
invalidate_todo_links_index_cache();
367427
bump_todo_version();
368428
}
369429
}
@@ -768,9 +828,8 @@ impl TodoPlugin {
768828
let Some(todo) = matches.first() else {
769829
return Vec::new();
770830
};
771-
let notes = load_notes().unwrap_or_default();
772831
let todos = guard.clone();
773-
let index = build_index_from_notes_and_todos(&notes, &todos);
832+
let (notes, index) = get_todo_links_index(&todos);
774833
let source = EntityKey::new(LinkTarget::Todo, todo.id.clone());
775834
let mut actions: Vec<Action> = index
776835
.get_forward_links(&source)
@@ -1330,6 +1389,52 @@ mod tests {
13301389
}
13311390
}
13321391

1392+
#[test]
1393+
fn todo_links_reuses_cached_index_between_queries() {
1394+
reset_todo_links_index_cache_state();
1395+
let original = set_todos(vec![TodoEntry {
1396+
id: "t-cache".into(),
1397+
text: "cache me".into(),
1398+
done: false,
1399+
priority: 1,
1400+
tags: vec![],
1401+
entity_refs: vec![EntityRef::new(EntityKind::Note, "alpha", None)],
1402+
}]);
1403+
1404+
let plugin = TodoPlugin {
1405+
matcher: SkimMatcherV2::default(),
1406+
data: TODO_DATA.clone(),
1407+
cache: TODO_CACHE.clone(),
1408+
watcher: None,
1409+
};
1410+
1411+
let first = plugin.search_internal("todo links id:t-cache");
1412+
assert!(!first.is_empty());
1413+
assert_eq!(todo_links_index_rebuild_count(), 1);
1414+
1415+
let second = plugin.search_internal("todo links id:t-cache");
1416+
assert!(!second.is_empty());
1417+
assert_eq!(todo_links_index_rebuild_count(), 1);
1418+
1419+
update_cache(vec![TodoEntry {
1420+
id: "t-cache".into(),
1421+
text: "cache me updated".into(),
1422+
done: false,
1423+
priority: 1,
1424+
tags: vec![],
1425+
entity_refs: vec![EntityRef::new(EntityKind::Note, "alpha", None)],
1426+
}]);
1427+
1428+
let third = plugin.search_internal("todo links id:t-cache");
1429+
assert!(!third.is_empty());
1430+
assert_eq!(todo_links_index_rebuild_count(), 2);
1431+
1432+
if let Ok(mut guard) = TODO_DATA.write() {
1433+
*guard = original;
1434+
}
1435+
reset_todo_links_index_cache_state();
1436+
}
1437+
13331438
#[test]
13341439
fn todo_links_json_output_prefixes_machine_readable_row() {
13351440
let original = set_todos(vec![TodoEntry {

0 commit comments

Comments
 (0)