Skip to content

Commit cc0d29b

Browse files
authored
Merge pull request #892 from multiplex55/codex/refactor-note_panel.rs-refresh_derived-method
Split note derived recompute into fast (local) and heavy (global) paths
2 parents 73dd3ba + 27da3e3 commit cc0d29b

1 file changed

Lines changed: 122 additions & 53 deletions

File tree

src/gui/note_panel.rs

Lines changed: 122 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use crate::common::slug::slugify;
33
use crate::gui::LauncherApp;
44
use crate::plugin::Plugin;
55
use crate::plugins::note::{
6-
assets_dir, available_tags, image_files, note_cache_snapshot, resolve_note_query, save_note,
7-
Note, NoteExternalOpen, NotePlugin, NoteTarget,
6+
assets_dir, available_tags, image_files, note_cache_snapshot, note_version, resolve_note_query,
7+
save_note, Note, NoteExternalOpen, NotePlugin, NoteTarget,
88
};
9-
use crate::plugins::todo::{load_todos, TODO_FILE};
9+
use crate::plugins::todo::{load_todos, todo_version, TODO_FILE};
1010
use eframe::egui::{self, popup, Color32, FontId, Key};
1111
use egui_commonmark::{CommonMarkCache, CommonMarkViewer};
1212
use egui_toast::{Toast, ToastKind, ToastOptions};
@@ -23,10 +23,12 @@ use std::process::Command;
2323
use std::{
2424
env,
2525
path::{Path, PathBuf},
26+
time::Duration,
2627
};
2728
use url::Url;
2829

2930
const BACKLINK_PAGE_SIZE: usize = 12;
31+
const HEAVY_RECOMPUTE_IDLE_DEBOUNCE: Duration = Duration::from_millis(250);
3032

3133
#[derive(Clone, Copy, PartialEq, Eq)]
3234
enum BacklinkTab {
@@ -174,9 +176,13 @@ pub struct NotePanel {
174176
focus_textedit_next_frame: bool,
175177
last_textedit_id: Option<egui::Id>,
176178
derived: NoteDerivedView,
177-
derived_dirty: bool,
179+
fast_derived_dirty: bool,
180+
heavy_recompute_requested: bool,
181+
last_edit_at_secs: Option<f64>,
182+
last_notes_version: u64,
183+
last_todo_revision: u64,
178184
#[cfg(test)]
179-
derived_recompute_count: usize,
185+
heavy_recompute_count: usize,
180186
}
181187

182188
#[derive(Default, Clone)]
@@ -214,11 +220,16 @@ impl NotePanel {
214220
focus_textedit_next_frame: false,
215221
last_textedit_id: None,
216222
derived: NoteDerivedView::default(),
217-
derived_dirty: true,
223+
fast_derived_dirty: true,
224+
heavy_recompute_requested: true,
225+
last_edit_at_secs: None,
226+
last_notes_version: 0,
227+
last_todo_revision: 0,
218228
#[cfg(test)]
219-
derived_recompute_count: 0,
229+
heavy_recompute_count: 0,
220230
};
221-
panel.refresh_derived();
231+
panel.refresh_fast_derived();
232+
panel.refresh_heavy_derived(true);
222233
panel
223234
}
224235

@@ -230,46 +241,71 @@ impl NotePanel {
230241
}
231242
}
232243

233-
fn refresh_derived(&mut self) {
244+
fn refresh_fast_derived(&mut self) {
245+
self.derived.tags = extract_tags(&self.note.content);
246+
self.derived.wiki_links = extract_wiki_links(&self.note.content)
247+
.into_iter()
248+
.filter(|l| slugify(l) != self.note.slug)
249+
.collect();
250+
self.derived.external_links = extract_links(&self.note.content);
251+
self.fast_derived_dirty = false;
252+
}
253+
254+
fn refresh_heavy_derived(&mut self, force: bool) {
255+
let current_notes_version = note_version();
256+
let current_todo_revision = todo_version();
257+
if !force
258+
&& self.last_notes_version == current_notes_version
259+
&& self.last_todo_revision == current_todo_revision
260+
{
261+
self.heavy_recompute_requested = false;
262+
return;
263+
}
264+
234265
let todos = load_todos(TODO_FILE).unwrap_or_default();
235-
let todo_label_map = todos
266+
self.derived.todo_label_map = todos
236267
.iter()
237268
.filter(|t| !t.id.is_empty())
238269
.map(|t| (t.id.clone(), t.text.clone()))
239270
.collect::<HashMap<_, _>>();
240-
let notes = note_cache_snapshot();
241271

242-
self.derived = NoteDerivedView {
243-
tags: extract_tags(&self.note.content),
244-
wiki_links: extract_wiki_links(&self.note.content)
245-
.into_iter()
246-
.filter(|l| slugify(l) != self.note.slug)
247-
.collect(),
248-
external_links: extract_links(&self.note.content),
249-
backlink_rows_linked_todos: backlink_rows_for_note(
250-
&self.note.slug,
251-
BacklinkTab::LinkedTodos,
252-
&todos,
253-
&notes,
254-
),
255-
backlink_rows_related_notes: backlink_rows_for_note(
256-
&self.note.slug,
257-
BacklinkTab::RelatedNotes,
258-
&todos,
259-
&notes,
260-
),
261-
backlink_rows_mentions: backlink_rows_for_note(
262-
&self.note.slug,
263-
BacklinkTab::Mentions,
264-
&todos,
265-
&notes,
266-
),
267-
todo_label_map,
268-
};
269-
self.derived_dirty = false;
272+
let notes = note_cache_snapshot();
273+
self.derived.backlink_rows_linked_todos =
274+
backlink_rows_for_note(&self.note.slug, BacklinkTab::LinkedTodos, &todos, &notes);
275+
self.derived.backlink_rows_related_notes =
276+
backlink_rows_for_note(&self.note.slug, BacklinkTab::RelatedNotes, &todos, &notes);
277+
self.derived.backlink_rows_mentions =
278+
backlink_rows_for_note(&self.note.slug, BacklinkTab::Mentions, &todos, &notes);
279+
280+
self.last_notes_version = current_notes_version;
281+
self.last_todo_revision = current_todo_revision;
282+
self.heavy_recompute_requested = false;
270283
#[cfg(test)]
271284
{
272-
self.derived_recompute_count += 1;
285+
self.heavy_recompute_count += 1;
286+
}
287+
}
288+
289+
fn mark_content_changed(&mut self, now_secs: f64) {
290+
self.fast_derived_dirty = true;
291+
self.heavy_recompute_requested = true;
292+
self.last_edit_at_secs = Some(now_secs);
293+
}
294+
295+
fn maybe_refresh_heavy_derived(&mut self, ctx: &egui::Context) {
296+
let notes_changed = self.last_notes_version != note_version();
297+
let todos_changed = self.last_todo_revision != todo_version();
298+
let debounce_elapsed = self
299+
.last_edit_at_secs
300+
.map(|t| ctx.input(|i| i.time - t) >= HEAVY_RECOMPUTE_IDLE_DEBOUNCE.as_secs_f64())
301+
.unwrap_or(false);
302+
if notes_changed || todos_changed || debounce_elapsed {
303+
self.refresh_heavy_derived(false);
304+
return;
305+
}
306+
307+
if self.heavy_recompute_requested {
308+
ctx.request_repaint_after(HEAVY_RECOMPUTE_IDLE_DEBOUNCE);
273309
}
274310
}
275311

@@ -392,9 +428,10 @@ impl NotePanel {
392428
app.note_font_size += 1.0;
393429
}
394430
});
395-
if self.derived_dirty {
396-
self.refresh_derived();
431+
if self.fast_derived_dirty {
432+
self.refresh_fast_derived();
397433
}
434+
self.maybe_refresh_heavy_derived(ctx);
398435
if !self.derived.tags.is_empty() {
399436
let was_focused = self
400437
.last_textedit_id
@@ -667,7 +704,7 @@ impl NotePanel {
667704
}
668705
if modified {
669706
self.markdown_cache.clear_scrollable();
670-
self.derived_dirty = true;
707+
self.mark_content_changed(ctx.input(|i| i.time));
671708
}
672709
None
673710
} else {
@@ -687,7 +724,7 @@ impl NotePanel {
687724
if !self.preview_mode {
688725
if let Some(resp) = resp.inner {
689726
if resp.changed() {
690-
self.derived_dirty = true;
727+
self.mark_content_changed(ctx.input(|i| i.time));
691728
}
692729
let first_edit_frame = self.last_textedit_id.is_none();
693730
self.last_textedit_id = Some(resp.id);
@@ -874,7 +911,8 @@ impl NotePanel {
874911
if let Err(e) = save_note(&mut self.note, true) {
875912
app.set_error(format!("Failed to save note: {e}"));
876913
} else {
877-
self.refresh_derived();
914+
self.refresh_fast_derived();
915+
self.refresh_heavy_derived(true);
878916
self.finish_save(app);
879917
self.overwrite_prompt = false;
880918
}
@@ -885,7 +923,8 @@ impl NotePanel {
885923
if let Err(e) = save_note(&mut self.note, true) {
886924
app.set_error(format!("Failed to save note: {e}"));
887925
} else {
888-
self.refresh_derived();
926+
self.refresh_fast_derived();
927+
self.refresh_heavy_derived(true);
889928
self.finish_save(app);
890929
self.overwrite_prompt = false;
891930
}
@@ -908,15 +947,17 @@ impl NotePanel {
908947
.map(|l| slugify(&l))
909948
.filter(|l| l != &self.note.slug)
910949
.collect();
911-
self.derived_dirty = true;
950+
self.fast_derived_dirty = true;
951+
self.heavy_recompute_requested = true;
912952
if let Some(first) = self.note.content.lines().next() {
913953
if let Some(t) = first.strip_prefix("# ") {
914954
self.note.title = t.to_string();
915955
}
916956
}
917957
match save_note(&mut self.note, app.note_always_overwrite) {
918958
Ok(true) => {
919-
self.refresh_derived();
959+
self.refresh_fast_derived();
960+
self.refresh_heavy_derived(true);
920961
self.finish_save(app);
921962
}
922963
Ok(false) => {
@@ -2045,14 +2086,41 @@ Body with [[Other]]"
20452086
entity_refs: Vec::new(),
20462087
};
20472088
let mut panel = NotePanel::from_note(note);
2048-
let initial = panel.derived_recompute_count;
2089+
let initial = panel.heavy_recompute_count;
20492090
let _ = ctx.run(Default::default(), |ctx| {
20502091
panel.ui(ctx, &mut app);
20512092
});
20522093
let _ = ctx.run(Default::default(), |ctx| {
20532094
panel.ui(ctx, &mut app);
20542095
});
2055-
assert_eq!(panel.derived_recompute_count, initial);
2096+
assert_eq!(panel.heavy_recompute_count, initial);
2097+
}
2098+
2099+
#[test]
2100+
fn edits_do_not_trigger_heavy_recompute_every_frame() {
2101+
let ctx = egui::Context::default();
2102+
let mut app = new_app(&ctx);
2103+
let note = Note {
2104+
title: "Title".into(),
2105+
path: std::path::PathBuf::new(),
2106+
content: "# Title\n\nBody".into(),
2107+
tags: Vec::new(),
2108+
links: Vec::new(),
2109+
slug: "title".into(),
2110+
alias: None,
2111+
entity_refs: Vec::new(),
2112+
};
2113+
let mut panel = NotePanel::from_note(note);
2114+
let initial = panel.heavy_recompute_count;
2115+
panel.mark_content_changed(f64::MAX);
2116+
2117+
for _ in 0..3 {
2118+
let _ = ctx.run(Default::default(), |ctx| {
2119+
panel.ui(ctx, &mut app);
2120+
});
2121+
}
2122+
2123+
assert_eq!(panel.heavy_recompute_count, initial);
20562124
}
20572125

20582126
#[test]
@@ -2079,15 +2147,16 @@ Body with [[Other]]"
20792147
entity_refs: Vec::new(),
20802148
};
20812149
let mut panel = NotePanel::from_note(note);
2082-
let before = panel.derived_recompute_count;
2150+
let before = panel.heavy_recompute_count;
20832151
panel.note.content = "# Source
20842152
20852153
[[beta]]"
20862154
.into();
2087-
panel.derived_dirty = true;
2155+
panel.fast_derived_dirty = true;
2156+
panel.heavy_recompute_requested = true;
20882157
panel.save(&mut app);
20892158

2090-
assert!(panel.derived_recompute_count > before);
2159+
assert!(panel.heavy_recompute_count > before);
20912160
assert_eq!(panel.note.links, vec!["beta".to_string()]);
20922161

20932162
if let Some(p) = prev {

0 commit comments

Comments
 (0)