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