Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:42:13.316Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

bookmark_store.rs

753 lines · 26.6 KB · rust
1use std::{path::Path, sync::Arc};
2
3use collections::BTreeMap;
4use gpui::{Entity, TestAppContext};
5use language::Buffer;
6use project::{Project, bookmark_store::SerializedBookmark};
7use serde_json::json;
8use util::path;
9
10mod integration {
11    use super::*;
12    use fs::Fs as _;
13
14    fn init_test(cx: &mut TestAppContext) {
15        cx.update(|cx| {
16            let settings_store = settings::SettingsStore::test(cx);
17            cx.set_global(settings_store);
18            release_channel::init(semver::Version::new(0, 0, 0), cx);
19        });
20    }
21
22    fn project_path(path: &str) -> Arc<Path> {
23        Arc::from(Path::new(path))
24    }
25
26    fn serialized_bookmark(row: u32) -> SerializedBookmark {
27        SerializedBookmark {
28            row,
29            label: String::new(),
30        }
31    }
32
33    async fn open_buffer(
34        project: &Entity<Project>,
35        path: &str,
36        cx: &mut TestAppContext,
37    ) -> Entity<Buffer> {
38        project
39            .update(cx, |project, cx| {
40                project.open_local_buffer(Path::new(path), cx)
41            })
42            .await
43            .unwrap()
44    }
45
46    fn add_bookmarks(
47        project: &Entity<Project>,
48        buffer: &Entity<Buffer>,
49        rows: &[u32],
50        cx: &mut TestAppContext,
51    ) {
52        let buffer = buffer.clone();
53        project.update(cx, |project, cx| {
54            let bookmark_store = project.bookmark_store();
55            let snapshot = buffer.read(cx).snapshot();
56            for &row in rows {
57                let anchor = snapshot.anchor_after(text::Point::new(row, 0));
58                bookmark_store.update(cx, |store, cx| {
59                    store.toggle_bookmark(buffer.clone(), anchor, String::new(), cx);
60                });
61            }
62        });
63    }
64
65    fn add_labeled_bookmark(
66        project: &Entity<Project>,
67        buffer: &Entity<Buffer>,
68        row: u32,
69        label: &str,
70        cx: &mut TestAppContext,
71    ) {
72        let buffer = buffer.clone();
73        project.update(cx, |project, cx| {
74            let bookmark_store = project.bookmark_store();
75            let snapshot = buffer.read(cx).snapshot();
76            let anchor = snapshot.anchor_after(text::Point::new(row, 0));
77            bookmark_store.update(cx, |store, cx| {
78                store.toggle_bookmark(buffer.clone(), anchor, label.to_string(), cx);
79            });
80        });
81    }
82
83    fn get_all_bookmarks(
84        project: &Entity<Project>,
85        cx: &mut TestAppContext,
86    ) -> BTreeMap<Arc<Path>, Vec<SerializedBookmark>> {
87        project.read_with(cx, |project, cx| {
88            project
89                .bookmark_store()
90                .read(cx)
91                .all_serialized_bookmarks(cx)
92        })
93    }
94
95    fn build_serialized(
96        entries: &[(&str, &[u32])],
97    ) -> BTreeMap<Arc<Path>, Vec<SerializedBookmark>> {
98        let mut map = BTreeMap::new();
99        for &(path_str, rows) in entries {
100            let path = project_path(path_str);
101            map.insert(
102                path.clone(),
103                rows.iter().map(|&row| serialized_bookmark(row)).collect(),
104            );
105        }
106        map
107    }
108
109    async fn restore_bookmarks(
110        project: &Entity<Project>,
111        serialized: BTreeMap<Arc<Path>, Vec<SerializedBookmark>>,
112        cx: &mut TestAppContext,
113    ) {
114        project
115            .update(cx, |project, cx| {
116                project.bookmark_store().update(cx, |store, cx| {
117                    store.load_serialized_bookmarks(serialized, cx)
118                })
119            })
120            .await
121            .expect("with_serialized_bookmarks should succeed");
122    }
123
124    fn clear_bookmarks(project: &Entity<Project>, cx: &mut TestAppContext) {
125        project.update(cx, |project, cx| {
126            project.bookmark_store().update(cx, |store, cx| {
127                store.clear_bookmarks(cx);
128            });
129        });
130    }
131
132    fn assert_bookmark_rows(
133        bookmarks: &BTreeMap<Arc<Path>, Vec<SerializedBookmark>>,
134        path: &str,
135        expected_rows: &[u32],
136    ) {
137        let path = project_path(path);
138        let file_bookmarks = bookmarks
139            .get(&path)
140            .unwrap_or_else(|| panic!("Expected bookmarks for {}", path.display()));
141        let rows: Vec<u32> = file_bookmarks.iter().map(|b| b.row).collect();
142        assert_eq!(rows, expected_rows, "Bookmark rows for {}", path.display());
143    }
144
145    fn assert_bookmark_labels(
146        bookmarks: &BTreeMap<Arc<Path>, Vec<SerializedBookmark>>,
147        path: &str,
148        expected: &[(u32, &str)],
149    ) {
150        let path = project_path(path);
151        let file_bookmarks = bookmarks
152            .get(&path)
153            .unwrap_or_else(|| panic!("Expected bookmarks for {}", path.display()));
154        let actual: Vec<_> = file_bookmarks
155            .iter()
156            .map(|bookmark| (bookmark.row, bookmark.label.as_str()))
157            .collect();
158        assert_eq!(actual, expected, "Bookmark labels for {}", path.display());
159    }
160
161    #[gpui::test]
162    async fn test_all_serialized_bookmarks_empty(cx: &mut TestAppContext) {
163        init_test(cx);
164        cx.executor().allow_parking();
165
166        let fs = fs::FakeFs::new(cx.executor());
167        fs.insert_tree(path!("/project"), json!({"file1.rs": "line1\nline2\n"}))
168            .await;
169
170        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
171        assert!(get_all_bookmarks(&project, cx).is_empty());
172    }
173
174    #[gpui::test]
175    async fn test_all_serialized_bookmarks_single_file(cx: &mut TestAppContext) {
176        init_test(cx);
177        cx.executor().allow_parking();
178
179        let fs = fs::FakeFs::new(cx.executor());
180        fs.insert_tree(
181            path!("/project"),
182            json!({"file1.rs": "line1\nline2\nline3\nline4\nline5\n"}),
183        )
184        .await;
185
186        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
187        let buffer = open_buffer(&project, path!("/project/file1.rs"), cx).await;
188
189        add_bookmarks(&project, &buffer, &[0, 2], cx);
190
191        let bookmarks = get_all_bookmarks(&project, cx);
192        assert_eq!(bookmarks.len(), 1);
193        assert_bookmark_rows(&bookmarks, path!("/project/file1.rs"), &[0, 2]);
194    }
195
196    #[gpui::test]
197    async fn test_all_serialized_bookmarks_includes_labels(cx: &mut TestAppContext) {
198        init_test(cx);
199        cx.executor().allow_parking();
200
201        let fs = fs::FakeFs::new(cx.executor());
202        fs.insert_tree(
203            path!("/project"),
204            json!({"file1.rs": "line1\nline2\nline3\n"}),
205        )
206        .await;
207
208        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
209        let buffer = open_buffer(&project, path!("/project/file1.rs"), cx).await;
210
211        add_labeled_bookmark(&project, &buffer, 0, "first", cx);
212        add_labeled_bookmark(&project, &buffer, 2, "  keeps inner spaces  ", cx);
213
214        let bookmarks = get_all_bookmarks(&project, cx);
215        assert_bookmark_labels(
216            &bookmarks,
217            path!("/project/file1.rs"),
218            &[(0, "first"), (2, "  keeps inner spaces  ")],
219        );
220    }
221
222    #[gpui::test]
223    async fn test_all_serialized_bookmarks_multiple_files(cx: &mut TestAppContext) {
224        init_test(cx);
225        cx.executor().allow_parking();
226
227        let fs = fs::FakeFs::new(cx.executor());
228        fs.insert_tree(
229            path!("/project"),
230            json!({
231                "file1.rs": "line1\nline2\nline3\n",
232                "file2.rs": "lineA\nlineB\nlineC\nlineD\n",
233                "file3.rs": "single line"
234            }),
235        )
236        .await;
237
238        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
239        let buffer1 = open_buffer(&project, path!("/project/file1.rs"), cx).await;
240        let buffer2 = open_buffer(&project, path!("/project/file2.rs"), cx).await;
241        let _buffer3 = open_buffer(&project, path!("/project/file3.rs"), cx).await;
242
243        add_bookmarks(&project, &buffer1, &[1], cx);
244        add_bookmarks(&project, &buffer2, &[0, 3], cx);
245
246        let bookmarks = get_all_bookmarks(&project, cx);
247        assert_eq!(bookmarks.len(), 2);
248        assert_bookmark_rows(&bookmarks, path!("/project/file1.rs"), &[1]);
249        assert_bookmark_rows(&bookmarks, path!("/project/file2.rs"), &[0, 3]);
250        assert!(
251            !bookmarks.contains_key(&project_path(path!("/project/file3.rs"))),
252            "file3.rs should have no bookmarks"
253        );
254    }
255
256    #[gpui::test]
257    async fn test_all_serialized_bookmarks_after_toggle_off(cx: &mut TestAppContext) {
258        init_test(cx);
259        cx.executor().allow_parking();
260
261        let fs = fs::FakeFs::new(cx.executor());
262        fs.insert_tree(
263            path!("/project"),
264            json!({"file1.rs": "line1\nline2\nline3\n"}),
265        )
266        .await;
267
268        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
269        let buffer = open_buffer(&project, path!("/project/file1.rs"), cx).await;
270
271        add_bookmarks(&project, &buffer, &[1], cx);
272        assert_eq!(get_all_bookmarks(&project, cx).len(), 1);
273
274        // Toggle same row again to remove it
275        add_bookmarks(&project, &buffer, &[1], cx);
276        assert!(get_all_bookmarks(&project, cx).is_empty());
277    }
278
279    #[gpui::test]
280    async fn test_all_serialized_bookmarks_with_clear(cx: &mut TestAppContext) {
281        init_test(cx);
282        cx.executor().allow_parking();
283
284        let fs = fs::FakeFs::new(cx.executor());
285        fs.insert_tree(
286            path!("/project"),
287            json!({
288                "file1.rs": "line1\nline2\nline3\n",
289                "file2.rs": "lineA\nlineB\n"
290            }),
291        )
292        .await;
293
294        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
295        let buffer1 = open_buffer(&project, path!("/project/file1.rs"), cx).await;
296        let buffer2 = open_buffer(&project, path!("/project/file2.rs"), cx).await;
297
298        add_bookmarks(&project, &buffer1, &[0], cx);
299        add_bookmarks(&project, &buffer2, &[1], cx);
300        assert_eq!(get_all_bookmarks(&project, cx).len(), 2);
301
302        clear_bookmarks(&project, cx);
303        assert!(get_all_bookmarks(&project, cx).is_empty());
304    }
305
306    #[gpui::test]
307    async fn test_all_serialized_bookmarks_returns_sorted_by_path(cx: &mut TestAppContext) {
308        init_test(cx);
309        cx.executor().allow_parking();
310
311        let fs = fs::FakeFs::new(cx.executor());
312        fs.insert_tree(
313            path!("/project"),
314            json!({"b.rs": "line1\n", "a.rs": "line1\n", "c.rs": "line1\n"}),
315        )
316        .await;
317
318        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
319        let buffer_b = open_buffer(&project, path!("/project/b.rs"), cx).await;
320        let buffer_a = open_buffer(&project, path!("/project/a.rs"), cx).await;
321        let buffer_c = open_buffer(&project, path!("/project/c.rs"), cx).await;
322
323        add_bookmarks(&project, &buffer_b, &[0], cx);
324        add_bookmarks(&project, &buffer_a, &[0], cx);
325        add_bookmarks(&project, &buffer_c, &[0], cx);
326
327        let paths: Vec<_> = get_all_bookmarks(&project, cx).keys().cloned().collect();
328        assert_eq!(
329            paths,
330            [
331                project_path(path!("/project/a.rs")),
332                project_path(path!("/project/b.rs")),
333                project_path(path!("/project/c.rs")),
334            ]
335        );
336    }
337
338    #[gpui::test]
339    async fn test_all_serialized_bookmarks_deduplicates_same_row(cx: &mut TestAppContext) {
340        init_test(cx);
341        cx.executor().allow_parking();
342
343        let fs = fs::FakeFs::new(cx.executor());
344        fs.insert_tree(
345            path!("/project"),
346            json!({"file1.rs": "line1\nline2\nline3\nline4\n"}),
347        )
348        .await;
349
350        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
351        let buffer = open_buffer(&project, path!("/project/file1.rs"), cx).await;
352
353        add_bookmarks(&project, &buffer, &[1, 2], cx);
354
355        let bookmarks = get_all_bookmarks(&project, cx);
356        assert_bookmark_rows(&bookmarks, path!("/project/file1.rs"), &[1, 2]);
357
358        // Verify no duplicates
359        let rows: Vec<u32> = bookmarks
360            .get(&project_path(path!("/project/file1.rs")))
361            .unwrap()
362            .iter()
363            .map(|b| b.row)
364            .collect();
365        let mut deduped = rows.clone();
366        deduped.dedup();
367        assert_eq!(rows, deduped);
368    }
369
370    #[gpui::test]
371    async fn test_with_serialized_bookmarks_restores_bookmarks(cx: &mut TestAppContext) {
372        init_test(cx);
373        cx.executor().allow_parking();
374
375        let fs = fs::FakeFs::new(cx.executor());
376        fs.insert_tree(
377            path!("/project"),
378            json!({
379                "file1.rs": "line1\nline2\nline3\nline4\nline5\n",
380                "file2.rs": "aaa\nbbb\nccc\n"
381            }),
382        )
383        .await;
384
385        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
386
387        let serialized = build_serialized(&[
388            (path!("/project/file1.rs"), &[0, 3]),
389            (path!("/project/file2.rs"), &[1]),
390        ]);
391
392        restore_bookmarks(&project, serialized, cx).await;
393
394        let restored = get_all_bookmarks(&project, cx);
395        assert_eq!(restored.len(), 2);
396        assert_bookmark_rows(&restored, path!("/project/file1.rs"), &[0, 3]);
397        assert_bookmark_rows(&restored, path!("/project/file2.rs"), &[1]);
398    }
399
400    #[gpui::test]
401    async fn test_with_serialized_bookmarks_skips_out_of_range_rows(cx: &mut TestAppContext) {
402        init_test(cx);
403        cx.executor().allow_parking();
404
405        let fs = fs::FakeFs::new(cx.executor());
406        // 3 lines: rows 0, 1, 2
407        fs.insert_tree(
408            path!("/project"),
409            json!({"file1.rs": "line1\nline2\nline3"}),
410        )
411        .await;
412
413        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
414
415        let serialized = build_serialized(&[(path!("/project/file1.rs"), &[1, 100, 2])]);
416        restore_bookmarks(&project, serialized, cx).await;
417
418        // Before resolution, unloaded bookmarks are stored as-is
419        let unresolved = get_all_bookmarks(&project, cx);
420        assert_bookmark_rows(&unresolved, path!("/project/file1.rs"), &[1, 2, 100]);
421
422        // Open the buffer to trigger lazy resolution
423        let buffer = open_buffer(&project, path!("/project/file1.rs"), cx).await;
424        project.update(cx, |project, cx| {
425            let buffer_snapshot = buffer.read(cx).snapshot();
426            project.bookmark_store().update(cx, |store, cx| {
427                store.bookmarks_for_buffer(
428                    buffer.clone(),
429                    buffer_snapshot.anchor_before(0)
430                        ..buffer_snapshot.anchor_after(buffer_snapshot.len()),
431                    &buffer_snapshot,
432                    cx,
433                );
434            });
435        });
436
437        // After resolution, out-of-range rows are filtered
438        let restored = get_all_bookmarks(&project, cx);
439        assert_bookmark_rows(&restored, path!("/project/file1.rs"), &[1, 2]);
440    }
441
442    #[gpui::test]
443    async fn test_with_serialized_bookmarks_skips_empty_entries(cx: &mut TestAppContext) {
444        init_test(cx);
445        cx.executor().allow_parking();
446
447        let fs = fs::FakeFs::new(cx.executor());
448        fs.insert_tree(
449            path!("/project"),
450            json!({"file1.rs": "line1\nline2\n", "file2.rs": "aaa\nbbb\n"}),
451        )
452        .await;
453
454        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
455
456        let mut serialized = build_serialized(&[(path!("/project/file1.rs"), &[0])]);
457        serialized.insert(project_path(path!("/project/file2.rs")), vec![]);
458
459        restore_bookmarks(&project, serialized, cx).await;
460
461        let restored = get_all_bookmarks(&project, cx);
462        assert_eq!(restored.len(), 1);
463        assert!(restored.contains_key(&project_path(path!("/project/file1.rs"))));
464        assert!(!restored.contains_key(&project_path(path!("/project/file2.rs"))));
465    }
466
467    #[gpui::test]
468    async fn test_with_serialized_bookmarks_all_out_of_range_produces_no_entry(
469        cx: &mut TestAppContext,
470    ) {
471        init_test(cx);
472        cx.executor().allow_parking();
473
474        let fs = fs::FakeFs::new(cx.executor());
475        fs.insert_tree(path!("/project"), json!({"tiny.rs": "x"}))
476            .await;
477
478        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
479
480        let serialized = build_serialized(&[(path!("/project/tiny.rs"), &[5, 10])]);
481        restore_bookmarks(&project, serialized, cx).await;
482
483        // Before resolution, unloaded bookmarks are stored as-is
484        let unresolved = get_all_bookmarks(&project, cx);
485        assert_eq!(unresolved.len(), 1);
486
487        // Open the buffer to trigger lazy resolution
488        let buffer = open_buffer(&project, path!("/project/tiny.rs"), cx).await;
489        project.update(cx, |project, cx| {
490            let buffer_snapshot = buffer.read(cx).snapshot();
491            project.bookmark_store().update(cx, |store, cx| {
492                store.bookmarks_for_buffer(
493                    buffer.clone(),
494                    buffer_snapshot.anchor_before(0)
495                        ..buffer_snapshot.anchor_after(buffer_snapshot.len()),
496                    &buffer_snapshot,
497                    cx,
498                );
499            });
500        });
501
502        // After resolution, all out-of-range rows are filtered away
503        assert!(get_all_bookmarks(&project, cx).is_empty());
504    }
505
506    #[gpui::test]
507    async fn test_with_serialized_bookmarks_replaces_existing(cx: &mut TestAppContext) {
508        init_test(cx);
509        cx.executor().allow_parking();
510
511        let fs = fs::FakeFs::new(cx.executor());
512        fs.insert_tree(
513            path!("/project"),
514            json!({"file1.rs": "aaa\nbbb\nccc\nddd\n"}),
515        )
516        .await;
517
518        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
519        let buffer = open_buffer(&project, path!("/project/file1.rs"), cx).await;
520
521        add_bookmarks(&project, &buffer, &[0], cx);
522        assert_bookmark_rows(
523            &get_all_bookmarks(&project, cx),
524            path!("/project/file1.rs"),
525            &[0],
526        );
527
528        // Restoring different bookmarks should replace, not merge
529        let serialized = build_serialized(&[(path!("/project/file1.rs"), &[2, 3])]);
530        restore_bookmarks(&project, serialized, cx).await;
531
532        let after = get_all_bookmarks(&project, cx);
533        assert_eq!(after.len(), 1);
534        assert_bookmark_rows(&after, path!("/project/file1.rs"), &[2, 3]);
535    }
536
537    #[gpui::test]
538    async fn test_serialize_deserialize_round_trip(cx: &mut TestAppContext) {
539        init_test(cx);
540        cx.executor().allow_parking();
541
542        let fs = fs::FakeFs::new(cx.executor());
543        fs.insert_tree(
544            path!("/project"),
545            json!({
546                "alpha.rs": "fn main() {\n    println!(\"hello\");\n    return;\n}\n",
547                "beta.rs": "use std::io;\nfn read() {}\nfn write() {}\n"
548            }),
549        )
550        .await;
551
552        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
553        let buffer_alpha = open_buffer(&project, path!("/project/alpha.rs"), cx).await;
554        let buffer_beta = open_buffer(&project, path!("/project/beta.rs"), cx).await;
555
556        add_bookmarks(&project, &buffer_alpha, &[0, 2, 3], cx);
557        add_bookmarks(&project, &buffer_beta, &[1], cx);
558
559        // Serialize
560        let serialized = get_all_bookmarks(&project, cx);
561        assert_eq!(serialized.len(), 2);
562        assert_bookmark_rows(&serialized, path!("/project/alpha.rs"), &[0, 2, 3]);
563        assert_bookmark_rows(&serialized, path!("/project/beta.rs"), &[1]);
564
565        // Clear and restore
566        clear_bookmarks(&project, cx);
567        assert!(get_all_bookmarks(&project, cx).is_empty());
568
569        restore_bookmarks(&project, serialized, cx).await;
570
571        let restored = get_all_bookmarks(&project, cx);
572        assert_eq!(restored.len(), 2);
573        assert_bookmark_rows(&restored, path!("/project/alpha.rs"), &[0, 2, 3]);
574        assert_bookmark_rows(&restored, path!("/project/beta.rs"), &[1]);
575    }
576
577    #[gpui::test]
578    async fn test_round_trip_preserves_bookmarks_after_file_edit(cx: &mut TestAppContext) {
579        init_test(cx);
580        cx.executor().allow_parking();
581
582        let fs = fs::FakeFs::new(cx.executor());
583        fs.insert_tree(
584            path!("/project"),
585            json!({"file.rs": "aaa\nbbb\nccc\nddd\neee\n"}),
586        )
587        .await;
588
589        let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
590        let buffer = open_buffer(&project, path!("/project/file.rs"), cx).await;
591
592        add_bookmarks(&project, &buffer, &[1, 3], cx);
593
594        // Insert a line at the beginning, shifting bookmarks down by 1
595        buffer.update(cx, |buffer, cx| {
596            buffer.edit([(0..0, "new_first_line\n")], None, cx);
597        });
598
599        let serialized = get_all_bookmarks(&project, cx);
600        assert_bookmark_rows(&serialized, path!("/project/file.rs"), &[2, 4]);
601
602        // Clear and restore
603        clear_bookmarks(&project, cx);
604        restore_bookmarks(&project, serialized, cx).await;
605
606        let restored = get_all_bookmarks(&project, cx);
607        assert_bookmark_rows(&restored, path!("/project/file.rs"), &[2, 4]);
608    }
609
610    #[gpui::test]
611    async fn test_file_deletion_removes_bookmarks(cx: &mut TestAppContext) {
612        init_test(cx);
613        cx.executor().allow_parking();
614
615        let fs = fs::FakeFs::new(cx.executor());
616        fs.insert_tree(
617            path!("/project"),
618            json!({
619                "file1.rs": "aaa\nbbb\nccc\n",
620                "file2.rs": "ddd\neee\nfff\n"
621            }),
622        )
623        .await;
624
625        let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
626        let buffer1 = open_buffer(&project, path!("/project/file1.rs"), cx).await;
627        let buffer2 = open_buffer(&project, path!("/project/file2.rs"), cx).await;
628
629        add_bookmarks(&project, &buffer1, &[0, 2], cx);
630        add_bookmarks(&project, &buffer2, &[1], cx);
631        assert_eq!(get_all_bookmarks(&project, cx).len(), 2);
632
633        // Delete file1.rs
634        fs.remove_file(path!("/project/file1.rs").as_ref(), Default::default())
635            .await
636            .unwrap();
637        cx.executor().run_until_parked();
638
639        // file1.rs bookmarks should be gone, file2.rs bookmarks preserved
640        let bookmarks = get_all_bookmarks(&project, cx);
641        assert_eq!(bookmarks.len(), 1);
642        assert!(!bookmarks.contains_key(&project_path(path!("/project/file1.rs"))));
643        assert_bookmark_rows(&bookmarks, path!("/project/file2.rs"), &[1]);
644    }
645
646    #[gpui::test]
647    async fn test_deleting_all_bookmarked_files_clears_store(cx: &mut TestAppContext) {
648        init_test(cx);
649        cx.executor().allow_parking();
650
651        let fs = fs::FakeFs::new(cx.executor());
652        fs.insert_tree(
653            path!("/project"),
654            json!({
655                "file1.rs": "aaa\nbbb\n",
656                "file2.rs": "ccc\nddd\n"
657            }),
658        )
659        .await;
660
661        let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
662        let buffer1 = open_buffer(&project, path!("/project/file1.rs"), cx).await;
663        let buffer2 = open_buffer(&project, path!("/project/file2.rs"), cx).await;
664
665        add_bookmarks(&project, &buffer1, &[0], cx);
666        add_bookmarks(&project, &buffer2, &[1], cx);
667        assert_eq!(get_all_bookmarks(&project, cx).len(), 2);
668
669        // Delete both files
670        fs.remove_file(path!("/project/file1.rs").as_ref(), Default::default())
671            .await
672            .unwrap();
673        fs.remove_file(path!("/project/file2.rs").as_ref(), Default::default())
674            .await
675            .unwrap();
676        cx.executor().run_until_parked();
677
678        assert!(get_all_bookmarks(&project, cx).is_empty());
679    }
680
681    #[gpui::test]
682    async fn test_file_rename_re_keys_bookmarks(cx: &mut TestAppContext) {
683        init_test(cx);
684        cx.executor().allow_parking();
685
686        let fs = fs::FakeFs::new(cx.executor());
687        fs.insert_tree(path!("/project"), json!({"old_name.rs": "aaa\nbbb\nccc\n"}))
688            .await;
689
690        let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
691        let buffer = open_buffer(&project, path!("/project/old_name.rs"), cx).await;
692
693        add_bookmarks(&project, &buffer, &[0, 2], cx);
694        assert_bookmark_rows(
695            &get_all_bookmarks(&project, cx),
696            path!("/project/old_name.rs"),
697            &[0, 2],
698        );
699
700        // Rename the file
701        fs.rename(
702            path!("/project/old_name.rs").as_ref(),
703            path!("/project/new_name.rs").as_ref(),
704            Default::default(),
705        )
706        .await
707        .unwrap();
708        cx.executor().run_until_parked();
709
710        let bookmarks = get_all_bookmarks(&project, cx);
711        assert_eq!(bookmarks.len(), 1);
712        assert!(!bookmarks.contains_key(&project_path(path!("/project/old_name.rs"))));
713        assert_bookmark_rows(&bookmarks, path!("/project/new_name.rs"), &[0, 2]);
714    }
715
716    #[gpui::test]
717    async fn test_file_rename_preserves_other_bookmarks(cx: &mut TestAppContext) {
718        init_test(cx);
719        cx.executor().allow_parking();
720
721        let fs = fs::FakeFs::new(cx.executor());
722        fs.insert_tree(
723            path!("/project"),
724            json!({
725                "rename_me.rs": "aaa\nbbb\n",
726                "untouched.rs": "ccc\nddd\neee\n"
727            }),
728        )
729        .await;
730
731        let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
732        let buffer_rename = open_buffer(&project, path!("/project/rename_me.rs"), cx).await;
733        let buffer_other = open_buffer(&project, path!("/project/untouched.rs"), cx).await;
734
735        add_bookmarks(&project, &buffer_rename, &[1], cx);
736        add_bookmarks(&project, &buffer_other, &[0, 2], cx);
737
738        fs.rename(
739            path!("/project/rename_me.rs").as_ref(),
740            path!("/project/renamed.rs").as_ref(),
741            Default::default(),
742        )
743        .await
744        .unwrap();
745        cx.executor().run_until_parked();
746
747        let bookmarks = get_all_bookmarks(&project, cx);
748        assert_eq!(bookmarks.len(), 2);
749        assert_bookmark_rows(&bookmarks, path!("/project/renamed.rs"), &[1]);
750        assert_bookmark_rows(&bookmarks, path!("/project/untouched.rs"), &[0, 2]);
751    }
752}
753
Served at tenant.openagents/omega Member data and write actions are omitted.