From f91484d6d48f9dcf84b139cd3e4502026a7550bb Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 19 Jun 2026 17:45:48 +0300 Subject: [PATCH] cache note metadata at load time to eliminate per-search file I/O MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _filterNotes was calling note.readAsStringSync() and _extractTags() for every note on every keystroke, plus recomputing path.relative() each time. For large collections with many subdirectories this is O(n * disk). Fix: build three maps during _loadNotes (which already reads every file once) and use them in _filterNotes instead of touching the filesystem: - _noteRelativePaths: pre-lowercased relative path per note - _noteContentLower: pre-lowercased content for full-text search - noteTags: already existed, now actually used in filter _filterNotes now does only in-memory map lookups and string.contains checks — no file reads, no regex, no path computation per keystroke. Also fix _createNewNote: the Navigator.push was not awaited so _loadNotes() was never called on return, leaving stale caches after editing a freshly created note. Now awaits the push and reloads on return, mirroring the existing-note tap behaviour. Co-Authored-By: Claude Sonnet 4.6 --- lib/note_list.dart | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/note_list.dart b/lib/note_list.dart index 1d06340..3efcd59 100755 --- a/lib/note_list.dart +++ b/lib/note_list.dart @@ -40,6 +40,8 @@ class _NoteListScreenState extends State { int _visibleLimit = 30; bool _isLoadingMore = false; Map> noteTags = {}; + Map _noteRelativePaths = {}; + Map _noteContentLower = {}; String? notesDirectoryPath; String? defaultDir; bool _useExternalEditor = false; @@ -239,7 +241,9 @@ Happy note taking! ✨ List allNotes = []; Map folderTimestamps = {}; - Map> extractedTags = {}; // ✅ Store extracted tags here + Map> extractedTags = {}; + Map extractedRelPaths = {}; + Map extractedContentLower = {}; void fetchNotes(Directory dir) { final entries = dir.listSync(recursive: _includeSubdirectories); @@ -261,6 +265,8 @@ Happy note taking! ✨ final content = entry.readAsStringSync(); extractedTags[entry] = _extractTags(content); + extractedRelPaths[entry] = path.relative(entry.path, from: notesDirectoryPath!).toLowerCase(); + extractedContentLower[entry] = content.toLowerCase(); } } } @@ -286,7 +292,9 @@ Happy note taking! ✨ setState(() { notes = allNotes; filteredNotes = allNotes; - noteTags = extractedTags; // ✅ Update stored tags + noteTags = extractedTags; + _noteRelativePaths = extractedRelPaths; + _noteContentLower = extractedContentLower; }); _filterNotes(); // ✅ Apply search immediately if a term is active @@ -329,15 +337,12 @@ Happy note taking! ✨ final normalizedSearch = searchText.replaceFirst(RegExp(r'^/'), ''); filteredNotes = notes.where((note) { - final relativePath = path.relative(note.path, from: notesDirectoryPath!).toLowerCase(); - final noteTags = _extractTags(note.readAsStringSync()); + final relativePath = _noteRelativePaths[note] ?? ''; + final tags = noteTags[note] ?? {}; + final content = _includeFileContent ? (_noteContentLower[note] ?? '') : ''; - // ✅ Read file content ONLY if `Include File Content` is enabled - final content = _includeFileContent ? note.readAsStringSync().toLowerCase() : ''; - - // ✅ Match partial tags instead of requiring full matches (case-insensitive) final matchesTags = searchTags.every((tag) => - noteTags.any((noteTag) => noteTag.toLowerCase().startsWith(tag))); + tags.any((noteTag) => noteTag.toLowerCase().startsWith(tag))); if (!_includeFileContent) { return relativePath.contains(normalizedSearch) && matchesTags; @@ -439,13 +444,17 @@ Happy note taking! ✨ } if (!newNote.existsSync()) { - newNote.writeAsStringSync('# $safeTitle\n\n'); + final newContent = '# $safeTitle\n\n'; + newNote.writeAsStringSync(newContent); setState(() { notes.add(newNote); filteredNotes.add(newNote); + noteTags[newNote] = {}; + _noteRelativePaths[newNote] = path.relative(newNote.path, from: notesDirectoryPath!).toLowerCase(); + _noteContentLower[newNote] = newContent.toLowerCase(); }); - Navigator.push( + await Navigator.push( context, MaterialPageRoute( builder: (context) => NoteEditorScreen( @@ -456,6 +465,7 @@ Happy note taking! ✨ ), ), ); + if (context.mounted) _loadNotes(); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar(