1//todo!(workspace)
2
3// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
4// use call::ActiveCall;
5// use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
6// use editor::{Editor, ExcerptRange, MultiBuffer};
7// use gpui::{point, BackgroundExecutor, TestAppContext, View, VisualTestContext, WindowContext};
8// use live_kit_client::MacOSDisplay;
9// use project::project_settings::ProjectSettings;
10// use rpc::proto::PeerId;
11// use serde_json::json;
12// use settings::SettingsStore;
13// use std::borrow::Cow;
14// use workspace::{
15// dock::{test::TestPanel, DockPosition},
16// item::{test::TestItem, ItemHandle as _},
17// shared_screen::SharedScreen,
18// SplitDirection, Workspace,
19// };
20
21// #[gpui::test(iterations = 10)]
22// async fn test_basic_following(
23// executor: BackgroundExecutor,
24// cx_a: &mut TestAppContext,
25// cx_b: &mut TestAppContext,
26// cx_c: &mut TestAppContext,
27// cx_d: &mut TestAppContext,
28// ) {
29// let mut server = TestServer::start(executor.clone()).await;
30// let client_a = server.create_client(cx_a, "user_a").await;
31// let client_b = server.create_client(cx_b, "user_b").await;
32// let client_c = server.create_client(cx_c, "user_c").await;
33// let client_d = server.create_client(cx_d, "user_d").await;
34// server
35// .create_room(&mut [
36// (&client_a, cx_a),
37// (&client_b, cx_b),
38// (&client_c, cx_c),
39// (&client_d, cx_d),
40// ])
41// .await;
42// let active_call_a = cx_a.read(ActiveCall::global);
43// let active_call_b = cx_b.read(ActiveCall::global);
44
45// cx_a.update(editor::init);
46// cx_b.update(editor::init);
47
48// client_a
49// .fs()
50// .insert_tree(
51// "/a",
52// json!({
53// "1.txt": "one\none\none",
54// "2.txt": "two\ntwo\ntwo",
55// "3.txt": "three\nthree\nthree",
56// }),
57// )
58// .await;
59// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
60// active_call_a
61// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
62// .await
63// .unwrap();
64
65// let project_id = active_call_a
66// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
67// .await
68// .unwrap();
69// let project_b = client_b.build_remote_project(project_id, cx_b).await;
70// active_call_b
71// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
72// .await
73// .unwrap();
74
75// let window_a = client_a.build_workspace(&project_a, cx_a);
76// let workspace_a = window_a.root(cx_a).unwrap();
77// let window_b = client_b.build_workspace(&project_b, cx_b);
78// let workspace_b = window_b.root(cx_b).unwrap();
79
80// todo!("could be wrong")
81// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
82// let cx_a = &mut cx_a;
83// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
84// let cx_b = &mut cx_b;
85// let mut cx_c = VisualTestContext::from_window(*window_c, cx_c);
86// let cx_c = &mut cx_c;
87// let mut cx_d = VisualTestContext::from_window(*window_d, cx_d);
88// let cx_d = &mut cx_d;
89
90// // Client A opens some editors.
91// let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
92// let editor_a1 = workspace_a
93// .update(cx_a, |workspace, cx| {
94// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
95// })
96// .await
97// .unwrap()
98// .downcast::<Editor>()
99// .unwrap();
100// let editor_a2 = workspace_a
101// .update(cx_a, |workspace, cx| {
102// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
103// })
104// .await
105// .unwrap()
106// .downcast::<Editor>()
107// .unwrap();
108
109// // Client B opens an editor.
110// let editor_b1 = workspace_b
111// .update(cx_b, |workspace, cx| {
112// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
113// })
114// .await
115// .unwrap()
116// .downcast::<Editor>()
117// .unwrap();
118
119// let peer_id_a = client_a.peer_id().unwrap();
120// let peer_id_b = client_b.peer_id().unwrap();
121// let peer_id_c = client_c.peer_id().unwrap();
122// let peer_id_d = client_d.peer_id().unwrap();
123
124// // Client A updates their selections in those editors
125// editor_a1.update(cx_a, |editor, cx| {
126// editor.handle_input("a", cx);
127// editor.handle_input("b", cx);
128// editor.handle_input("c", cx);
129// editor.select_left(&Default::default(), cx);
130// assert_eq!(editor.selections.ranges(cx), vec![3..2]);
131// });
132// editor_a2.update(cx_a, |editor, cx| {
133// editor.handle_input("d", cx);
134// editor.handle_input("e", cx);
135// editor.select_left(&Default::default(), cx);
136// assert_eq!(editor.selections.ranges(cx), vec![2..1]);
137// });
138
139// // When client B starts following client A, all visible view states are replicated to client B.
140// workspace_b
141// .update(cx_b, |workspace, cx| {
142// workspace.follow(peer_id_a, cx).unwrap()
143// })
144// .await
145// .unwrap();
146
147// cx_c.executor().run_until_parked();
148// let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
149// workspace
150// .active_item(cx)
151// .unwrap()
152// .downcast::<Editor>()
153// .unwrap()
154// });
155// assert_eq!(
156// cx_b.read(|cx| editor_b2.project_path(cx)),
157// Some((worktree_id, "2.txt").into())
158// );
159// assert_eq!(
160// editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
161// vec![2..1]
162// );
163// assert_eq!(
164// editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
165// vec![3..2]
166// );
167
168// cx_c.executor().run_until_parked();
169// let active_call_c = cx_c.read(ActiveCall::global);
170// let project_c = client_c.build_remote_project(project_id, cx_c).await;
171// let window_c = client_c.build_workspace(&project_c, cx_c);
172// let workspace_c = window_c.root(cx_c).unwrap();
173// active_call_c
174// .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
175// .await
176// .unwrap();
177// drop(project_c);
178
179// // Client C also follows client A.
180// workspace_c
181// .update(cx_c, |workspace, cx| {
182// workspace.follow(peer_id_a, cx).unwrap()
183// })
184// .await
185// .unwrap();
186
187// cx_d.executor().run_until_parked();
188// let active_call_d = cx_d.read(ActiveCall::global);
189// let project_d = client_d.build_remote_project(project_id, cx_d).await;
190// let workspace_d = client_d
191// .build_workspace(&project_d, cx_d)
192// .root(cx_d)
193// .unwrap();
194// active_call_d
195// .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
196// .await
197// .unwrap();
198// drop(project_d);
199
200// // All clients see that clients B and C are following client A.
201// cx_c.executor().run_until_parked();
202// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
203// assert_eq!(
204// followers_by_leader(project_id, cx),
205// &[(peer_id_a, vec![peer_id_b, peer_id_c])],
206// "followers seen by {name}"
207// );
208// }
209
210// // Client C unfollows client A.
211// workspace_c.update(cx_c, |workspace, cx| {
212// workspace.unfollow(&workspace.active_pane().clone(), cx);
213// });
214
215// // All clients see that clients B is following client A.
216// cx_c.executor().run_until_parked();
217// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
218// assert_eq!(
219// followers_by_leader(project_id, cx),
220// &[(peer_id_a, vec![peer_id_b])],
221// "followers seen by {name}"
222// );
223// }
224
225// // Client C re-follows client A.
226// workspace_c
227// .update(cx_c, |workspace, cx| {
228// workspace.follow(peer_id_a, cx).unwrap()
229// })
230// .await
231// .unwrap();
232
233// // All clients see that clients B and C are following client A.
234// cx_c.executor().run_until_parked();
235// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
236// assert_eq!(
237// followers_by_leader(project_id, cx),
238// &[(peer_id_a, vec![peer_id_b, peer_id_c])],
239// "followers seen by {name}"
240// );
241// }
242
243// // Client D follows client B, then switches to following client C.
244// workspace_d
245// .update(cx_d, |workspace, cx| {
246// workspace.follow(peer_id_b, cx).unwrap()
247// })
248// .await
249// .unwrap();
250// workspace_d
251// .update(cx_d, |workspace, cx| {
252// workspace.follow(peer_id_c, cx).unwrap()
253// })
254// .await
255// .unwrap();
256
257// // All clients see that D is following C
258// cx_d.executor().run_until_parked();
259// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
260// assert_eq!(
261// followers_by_leader(project_id, cx),
262// &[
263// (peer_id_a, vec![peer_id_b, peer_id_c]),
264// (peer_id_c, vec![peer_id_d])
265// ],
266// "followers seen by {name}"
267// );
268// }
269
270// // Client C closes the project.
271// window_c.remove(cx_c);
272// cx_c.drop_last(workspace_c);
273
274// // Clients A and B see that client B is following A, and client C is not present in the followers.
275// cx_c.executor().run_until_parked();
276// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
277// assert_eq!(
278// followers_by_leader(project_id, cx),
279// &[(peer_id_a, vec![peer_id_b]),],
280// "followers seen by {name}"
281// );
282// }
283
284// // When client A activates a different editor, client B does so as well.
285// workspace_a.update(cx_a, |workspace, cx| {
286// workspace.activate_item(&editor_a1, cx)
287// });
288// executor.run_until_parked();
289// workspace_b.update(cx_b, |workspace, cx| {
290// assert_eq!(
291// workspace.active_item(cx).unwrap().item_id(),
292// editor_b1.item_id()
293// );
294// });
295
296// // When client A opens a multibuffer, client B does so as well.
297// let multibuffer_a = cx_a.build_model(|cx| {
298// let buffer_a1 = project_a.update(cx, |project, cx| {
299// project
300// .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
301// .unwrap()
302// });
303// let buffer_a2 = project_a.update(cx, |project, cx| {
304// project
305// .get_open_buffer(&(worktree_id, "2.txt").into(), cx)
306// .unwrap()
307// });
308// let mut result = MultiBuffer::new(0);
309// result.push_excerpts(
310// buffer_a1,
311// [ExcerptRange {
312// context: 0..3,
313// primary: None,
314// }],
315// cx,
316// );
317// result.push_excerpts(
318// buffer_a2,
319// [ExcerptRange {
320// context: 4..7,
321// primary: None,
322// }],
323// cx,
324// );
325// result
326// });
327// let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
328// let editor =
329// cx.build_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
330// workspace.add_item(Box::new(editor.clone()), cx);
331// editor
332// });
333// executor.run_until_parked();
334// let multibuffer_editor_b = workspace_b.update(cx_b, |workspace, cx| {
335// workspace
336// .active_item(cx)
337// .unwrap()
338// .downcast::<Editor>()
339// .unwrap()
340// });
341// assert_eq!(
342// multibuffer_editor_a.update(cx_a, |editor, cx| editor.text(cx)),
343// multibuffer_editor_b.update(cx_b, |editor, cx| editor.text(cx)),
344// );
345
346// // When client A navigates back and forth, client B does so as well.
347// workspace_a
348// .update(cx_a, |workspace, cx| {
349// workspace.go_back(workspace.active_pane().downgrade(), cx)
350// })
351// .await
352// .unwrap();
353// executor.run_until_parked();
354// workspace_b.update(cx_b, |workspace, cx| {
355// assert_eq!(
356// workspace.active_item(cx).unwrap().item_id(),
357// editor_b1.item_id()
358// );
359// });
360
361// workspace_a
362// .update(cx_a, |workspace, cx| {
363// workspace.go_back(workspace.active_pane().downgrade(), cx)
364// })
365// .await
366// .unwrap();
367// executor.run_until_parked();
368// workspace_b.update(cx_b, |workspace, cx| {
369// assert_eq!(
370// workspace.active_item(cx).unwrap().item_id(),
371// editor_b2.item_id()
372// );
373// });
374
375// workspace_a
376// .update(cx_a, |workspace, cx| {
377// workspace.go_forward(workspace.active_pane().downgrade(), cx)
378// })
379// .await
380// .unwrap();
381// executor.run_until_parked();
382// workspace_b.update(cx_b, |workspace, cx| {
383// assert_eq!(
384// workspace.active_item(cx).unwrap().item_id(),
385// editor_b1.item_id()
386// );
387// });
388
389// // Changes to client A's editor are reflected on client B.
390// editor_a1.update(cx_a, |editor, cx| {
391// editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
392// });
393// executor.run_until_parked();
394// editor_b1.update(cx_b, |editor, cx| {
395// assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
396// });
397
398// editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
399// executor.run_until_parked();
400// editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
401
402// editor_a1.update(cx_a, |editor, cx| {
403// editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
404// editor.set_scroll_position(point(0., 100.), cx);
405// });
406// executor.run_until_parked();
407// editor_b1.update(cx_b, |editor, cx| {
408// assert_eq!(editor.selections.ranges(cx), &[3..3]);
409// });
410
411// // After unfollowing, client B stops receiving updates from client A.
412// workspace_b.update(cx_b, |workspace, cx| {
413// workspace.unfollow(&workspace.active_pane().clone(), cx)
414// });
415// workspace_a.update(cx_a, |workspace, cx| {
416// workspace.activate_item(&editor_a2, cx)
417// });
418// executor.run_until_parked();
419// assert_eq!(
420// workspace_b.update(cx_b, |workspace, cx| workspace
421// .active_item(cx)
422// .unwrap()
423// .item_id()),
424// editor_b1.item_id()
425// );
426
427// // Client A starts following client B.
428// workspace_a
429// .update(cx_a, |workspace, cx| {
430// workspace.follow(peer_id_b, cx).unwrap()
431// })
432// .await
433// .unwrap();
434// assert_eq!(
435// workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
436// Some(peer_id_b)
437// );
438// assert_eq!(
439// workspace_a.update(cx_a, |workspace, cx| workspace
440// .active_item(cx)
441// .unwrap()
442// .item_id()),
443// editor_a1.item_id()
444// );
445
446// // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
447// let display = MacOSDisplay::new();
448// active_call_b
449// .update(cx_b, |call, cx| call.set_location(None, cx))
450// .await
451// .unwrap();
452// active_call_b
453// .update(cx_b, |call, cx| {
454// call.room().unwrap().update(cx, |room, cx| {
455// room.set_display_sources(vec![display.clone()]);
456// room.share_screen(cx)
457// })
458// })
459// .await
460// .unwrap();
461// executor.run_until_parked();
462// let shared_screen = workspace_a.update(cx_a, |workspace, cx| {
463// workspace
464// .active_item(cx)
465// .expect("no active item")
466// .downcast::<SharedScreen>()
467// .expect("active item isn't a shared screen")
468// });
469
470// // Client B activates Zed again, which causes the previous editor to become focused again.
471// active_call_b
472// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
473// .await
474// .unwrap();
475// executor.run_until_parked();
476// workspace_a.update(cx_a, |workspace, cx| {
477// assert_eq!(
478// workspace.active_item(cx).unwrap().item_id(),
479// editor_a1.item_id()
480// )
481// });
482
483// // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
484// workspace_b.update(cx_b, |workspace, cx| {
485// workspace.activate_item(&multibuffer_editor_b, cx)
486// });
487// executor.run_until_parked();
488// workspace_a.update(cx_a, |workspace, cx| {
489// assert_eq!(
490// workspace.active_item(cx).unwrap().item_id(),
491// multibuffer_editor_a.item_id()
492// )
493// });
494
495// // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
496// let panel = window_b.build_view(cx_b, |_| TestPanel::new(DockPosition::Left));
497// workspace_b.update(cx_b, |workspace, cx| {
498// workspace.add_panel(panel, cx);
499// workspace.toggle_panel_focus::<TestPanel>(cx);
500// });
501// executor.run_until_parked();
502// assert_eq!(
503// workspace_a.update(cx_a, |workspace, cx| workspace
504// .active_item(cx)
505// .unwrap()
506// .item_id()),
507// shared_screen.item_id()
508// );
509
510// // Toggling the focus back to the pane causes client A to return to the multibuffer.
511// workspace_b.update(cx_b, |workspace, cx| {
512// workspace.toggle_panel_focus::<TestPanel>(cx);
513// });
514// executor.run_until_parked();
515// workspace_a.update(cx_a, |workspace, cx| {
516// assert_eq!(
517// workspace.active_item(cx).unwrap().item_id(),
518// multibuffer_editor_a.item_id()
519// )
520// });
521
522// // Client B activates an item that doesn't implement following,
523// // so the previously-opened screen-sharing item gets activated.
524// let unfollowable_item = window_b.build_view(cx_b, |_| TestItem::new());
525// workspace_b.update(cx_b, |workspace, cx| {
526// workspace.active_pane().update(cx, |pane, cx| {
527// pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
528// })
529// });
530// executor.run_until_parked();
531// assert_eq!(
532// workspace_a.update(cx_a, |workspace, cx| workspace
533// .active_item(cx)
534// .unwrap()
535// .item_id()),
536// shared_screen.item_id()
537// );
538
539// // Following interrupts when client B disconnects.
540// client_b.disconnect(&cx_b.to_async());
541// executor.advance_clock(RECONNECT_TIMEOUT);
542// assert_eq!(
543// workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
544// None
545// );
546// }
547
548// #[gpui::test]
549// async fn test_following_tab_order(
550// executor: BackgroundExecutor,
551// cx_a: &mut TestAppContext,
552// cx_b: &mut TestAppContext,
553// ) {
554// let mut server = TestServer::start(executor.clone()).await;
555// let client_a = server.create_client(cx_a, "user_a").await;
556// let client_b = server.create_client(cx_b, "user_b").await;
557// server
558// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
559// .await;
560// let active_call_a = cx_a.read(ActiveCall::global);
561// let active_call_b = cx_b.read(ActiveCall::global);
562
563// cx_a.update(editor::init);
564// cx_b.update(editor::init);
565
566// client_a
567// .fs()
568// .insert_tree(
569// "/a",
570// json!({
571// "1.txt": "one",
572// "2.txt": "two",
573// "3.txt": "three",
574// }),
575// )
576// .await;
577// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
578// active_call_a
579// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
580// .await
581// .unwrap();
582
583// let project_id = active_call_a
584// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
585// .await
586// .unwrap();
587// let project_b = client_b.build_remote_project(project_id, cx_b).await;
588// active_call_b
589// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
590// .await
591// .unwrap();
592
593// let workspace_a = client_a
594// .build_workspace(&project_a, cx_a)
595// .root(cx_a)
596// .unwrap();
597// let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
598
599// let workspace_b = client_b
600// .build_workspace(&project_b, cx_b)
601// .root(cx_b)
602// .unwrap();
603// let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
604
605// let client_b_id = project_a.update(cx_a, |project, _| {
606// project.collaborators().values().next().unwrap().peer_id
607// });
608
609// //Open 1, 3 in that order on client A
610// workspace_a
611// .update(cx_a, |workspace, cx| {
612// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
613// })
614// .await
615// .unwrap();
616// workspace_a
617// .update(cx_a, |workspace, cx| {
618// workspace.open_path((worktree_id, "3.txt"), None, true, cx)
619// })
620// .await
621// .unwrap();
622
623// let pane_paths = |pane: &View<workspace::Pane>, cx: &mut TestAppContext| {
624// pane.update(cx, |pane, cx| {
625// pane.items()
626// .map(|item| {
627// item.project_path(cx)
628// .unwrap()
629// .path
630// .to_str()
631// .unwrap()
632// .to_owned()
633// })
634// .collect::<Vec<_>>()
635// })
636// };
637
638// //Verify that the tabs opened in the order we expect
639// assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
640
641// //Follow client B as client A
642// workspace_a
643// .update(cx_a, |workspace, cx| {
644// workspace.follow(client_b_id, cx).unwrap()
645// })
646// .await
647// .unwrap();
648
649// //Open just 2 on client B
650// workspace_b
651// .update(cx_b, |workspace, cx| {
652// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
653// })
654// .await
655// .unwrap();
656// executor.run_until_parked();
657
658// // Verify that newly opened followed file is at the end
659// assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
660
661// //Open just 1 on client B
662// workspace_b
663// .update(cx_b, |workspace, cx| {
664// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
665// })
666// .await
667// .unwrap();
668// assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
669// executor.run_until_parked();
670
671// // Verify that following into 1 did not reorder
672// assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
673// }
674
675// #[gpui::test(iterations = 10)]
676// async fn test_peers_following_each_other(
677// executor: BackgroundExecutor,
678// cx_a: &mut TestAppContext,
679// cx_b: &mut TestAppContext,
680// ) {
681// let mut server = TestServer::start(executor.clone()).await;
682// let client_a = server.create_client(cx_a, "user_a").await;
683// let client_b = server.create_client(cx_b, "user_b").await;
684// server
685// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
686// .await;
687// let active_call_a = cx_a.read(ActiveCall::global);
688// let active_call_b = cx_b.read(ActiveCall::global);
689
690// cx_a.update(editor::init);
691// cx_b.update(editor::init);
692
693// // Client A shares a project.
694// client_a
695// .fs()
696// .insert_tree(
697// "/a",
698// json!({
699// "1.txt": "one",
700// "2.txt": "two",
701// "3.txt": "three",
702// "4.txt": "four",
703// }),
704// )
705// .await;
706// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
707// active_call_a
708// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
709// .await
710// .unwrap();
711// let project_id = active_call_a
712// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
713// .await
714// .unwrap();
715
716// // Client B joins the project.
717// let project_b = client_b.build_remote_project(project_id, cx_b).await;
718// active_call_b
719// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
720// .await
721// .unwrap();
722
723// // Client A opens a file.
724// let workspace_a = client_a
725// .build_workspace(&project_a, cx_a)
726// .root(cx_a)
727// .unwrap();
728// workspace_a
729// .update(cx_a, |workspace, cx| {
730// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
731// })
732// .await
733// .unwrap()
734// .downcast::<Editor>()
735// .unwrap();
736
737// // Client B opens a different file.
738// let workspace_b = client_b
739// .build_workspace(&project_b, cx_b)
740// .root(cx_b)
741// .unwrap();
742// workspace_b
743// .update(cx_b, |workspace, cx| {
744// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
745// })
746// .await
747// .unwrap()
748// .downcast::<Editor>()
749// .unwrap();
750
751// // Clients A and B follow each other in split panes
752// workspace_a.update(cx_a, |workspace, cx| {
753// workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
754// });
755// workspace_a
756// .update(cx_a, |workspace, cx| {
757// workspace.follow(client_b.peer_id().unwrap(), cx).unwrap()
758// })
759// .await
760// .unwrap();
761// workspace_b.update(cx_b, |workspace, cx| {
762// workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
763// });
764// workspace_b
765// .update(cx_b, |workspace, cx| {
766// workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
767// })
768// .await
769// .unwrap();
770
771// // Clients A and B return focus to the original files they had open
772// workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
773// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
774// executor.run_until_parked();
775
776// // Both clients see the other client's focused file in their right pane.
777// assert_eq!(
778// pane_summaries(&workspace_a, cx_a),
779// &[
780// PaneSummary {
781// active: true,
782// leader: None,
783// items: vec![(true, "1.txt".into())]
784// },
785// PaneSummary {
786// active: false,
787// leader: client_b.peer_id(),
788// items: vec![(false, "1.txt".into()), (true, "2.txt".into())]
789// },
790// ]
791// );
792// assert_eq!(
793// pane_summaries(&workspace_b, cx_b),
794// &[
795// PaneSummary {
796// active: true,
797// leader: None,
798// items: vec![(true, "2.txt".into())]
799// },
800// PaneSummary {
801// active: false,
802// leader: client_a.peer_id(),
803// items: vec![(false, "2.txt".into()), (true, "1.txt".into())]
804// },
805// ]
806// );
807
808// // Clients A and B each open a new file.
809// workspace_a
810// .update(cx_a, |workspace, cx| {
811// workspace.open_path((worktree_id, "3.txt"), None, true, cx)
812// })
813// .await
814// .unwrap();
815
816// workspace_b
817// .update(cx_b, |workspace, cx| {
818// workspace.open_path((worktree_id, "4.txt"), None, true, cx)
819// })
820// .await
821// .unwrap();
822// executor.run_until_parked();
823
824// // Both client's see the other client open the new file, but keep their
825// // focus on their own active pane.
826// assert_eq!(
827// pane_summaries(&workspace_a, cx_a),
828// &[
829// PaneSummary {
830// active: true,
831// leader: None,
832// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
833// },
834// PaneSummary {
835// active: false,
836// leader: client_b.peer_id(),
837// items: vec![
838// (false, "1.txt".into()),
839// (false, "2.txt".into()),
840// (true, "4.txt".into())
841// ]
842// },
843// ]
844// );
845// assert_eq!(
846// pane_summaries(&workspace_b, cx_b),
847// &[
848// PaneSummary {
849// active: true,
850// leader: None,
851// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
852// },
853// PaneSummary {
854// active: false,
855// leader: client_a.peer_id(),
856// items: vec![
857// (false, "2.txt".into()),
858// (false, "1.txt".into()),
859// (true, "3.txt".into())
860// ]
861// },
862// ]
863// );
864
865// // Client A focuses their right pane, in which they're following client B.
866// workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
867// executor.run_until_parked();
868
869// // Client B sees that client A is now looking at the same file as them.
870// assert_eq!(
871// pane_summaries(&workspace_a, cx_a),
872// &[
873// PaneSummary {
874// active: false,
875// leader: None,
876// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
877// },
878// PaneSummary {
879// active: true,
880// leader: client_b.peer_id(),
881// items: vec![
882// (false, "1.txt".into()),
883// (false, "2.txt".into()),
884// (true, "4.txt".into())
885// ]
886// },
887// ]
888// );
889// assert_eq!(
890// pane_summaries(&workspace_b, cx_b),
891// &[
892// PaneSummary {
893// active: true,
894// leader: None,
895// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
896// },
897// PaneSummary {
898// active: false,
899// leader: client_a.peer_id(),
900// items: vec![
901// (false, "2.txt".into()),
902// (false, "1.txt".into()),
903// (false, "3.txt".into()),
904// (true, "4.txt".into())
905// ]
906// },
907// ]
908// );
909
910// // Client B focuses their right pane, in which they're following client A,
911// // who is following them.
912// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
913// executor.run_until_parked();
914
915// // Client A sees that client B is now looking at the same file as them.
916// assert_eq!(
917// pane_summaries(&workspace_b, cx_b),
918// &[
919// PaneSummary {
920// active: false,
921// leader: None,
922// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
923// },
924// PaneSummary {
925// active: true,
926// leader: client_a.peer_id(),
927// items: vec![
928// (false, "2.txt".into()),
929// (false, "1.txt".into()),
930// (false, "3.txt".into()),
931// (true, "4.txt".into())
932// ]
933// },
934// ]
935// );
936// assert_eq!(
937// pane_summaries(&workspace_a, cx_a),
938// &[
939// PaneSummary {
940// active: false,
941// leader: None,
942// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
943// },
944// PaneSummary {
945// active: true,
946// leader: client_b.peer_id(),
947// items: vec![
948// (false, "1.txt".into()),
949// (false, "2.txt".into()),
950// (true, "4.txt".into())
951// ]
952// },
953// ]
954// );
955
956// // Client B focuses a file that they previously followed A to, breaking
957// // the follow.
958// workspace_b.update(cx_b, |workspace, cx| {
959// workspace.active_pane().update(cx, |pane, cx| {
960// pane.activate_prev_item(true, cx);
961// });
962// });
963// executor.run_until_parked();
964
965// // Both clients see that client B is looking at that previous file.
966// assert_eq!(
967// pane_summaries(&workspace_b, cx_b),
968// &[
969// PaneSummary {
970// active: false,
971// leader: None,
972// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
973// },
974// PaneSummary {
975// active: true,
976// leader: None,
977// items: vec![
978// (false, "2.txt".into()),
979// (false, "1.txt".into()),
980// (true, "3.txt".into()),
981// (false, "4.txt".into())
982// ]
983// },
984// ]
985// );
986// assert_eq!(
987// pane_summaries(&workspace_a, cx_a),
988// &[
989// PaneSummary {
990// active: false,
991// leader: None,
992// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
993// },
994// PaneSummary {
995// active: true,
996// leader: client_b.peer_id(),
997// items: vec![
998// (false, "1.txt".into()),
999// (false, "2.txt".into()),
1000// (false, "4.txt".into()),
1001// (true, "3.txt".into()),
1002// ]
1003// },
1004// ]
1005// );
1006
1007// // Client B closes tabs, some of which were originally opened by client A,
1008// // and some of which were originally opened by client B.
1009// workspace_b.update(cx_b, |workspace, cx| {
1010// workspace.active_pane().update(cx, |pane, cx| {
1011// pane.close_inactive_items(&Default::default(), cx)
1012// .unwrap()
1013// .detach();
1014// });
1015// });
1016
1017// executor.run_until_parked();
1018
1019// // Both clients see that Client B is looking at the previous tab.
1020// assert_eq!(
1021// pane_summaries(&workspace_b, cx_b),
1022// &[
1023// PaneSummary {
1024// active: false,
1025// leader: None,
1026// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1027// },
1028// PaneSummary {
1029// active: true,
1030// leader: None,
1031// items: vec![(true, "3.txt".into()),]
1032// },
1033// ]
1034// );
1035// assert_eq!(
1036// pane_summaries(&workspace_a, cx_a),
1037// &[
1038// PaneSummary {
1039// active: false,
1040// leader: None,
1041// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1042// },
1043// PaneSummary {
1044// active: true,
1045// leader: client_b.peer_id(),
1046// items: vec![
1047// (false, "1.txt".into()),
1048// (false, "2.txt".into()),
1049// (false, "4.txt".into()),
1050// (true, "3.txt".into()),
1051// ]
1052// },
1053// ]
1054// );
1055
1056// // Client B follows client A again.
1057// workspace_b
1058// .update(cx_b, |workspace, cx| {
1059// workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
1060// })
1061// .await
1062// .unwrap();
1063
1064// // Client A cycles through some tabs.
1065// workspace_a.update(cx_a, |workspace, cx| {
1066// workspace.active_pane().update(cx, |pane, cx| {
1067// pane.activate_prev_item(true, cx);
1068// });
1069// });
1070// executor.run_until_parked();
1071
1072// // Client B follows client A into those tabs.
1073// assert_eq!(
1074// pane_summaries(&workspace_a, cx_a),
1075// &[
1076// PaneSummary {
1077// active: false,
1078// leader: None,
1079// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1080// },
1081// PaneSummary {
1082// active: true,
1083// leader: None,
1084// items: vec![
1085// (false, "1.txt".into()),
1086// (false, "2.txt".into()),
1087// (true, "4.txt".into()),
1088// (false, "3.txt".into()),
1089// ]
1090// },
1091// ]
1092// );
1093// assert_eq!(
1094// pane_summaries(&workspace_b, cx_b),
1095// &[
1096// PaneSummary {
1097// active: false,
1098// leader: None,
1099// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1100// },
1101// PaneSummary {
1102// active: true,
1103// leader: client_a.peer_id(),
1104// items: vec![(false, "3.txt".into()), (true, "4.txt".into())]
1105// },
1106// ]
1107// );
1108
1109// workspace_a.update(cx_a, |workspace, cx| {
1110// workspace.active_pane().update(cx, |pane, cx| {
1111// pane.activate_prev_item(true, cx);
1112// });
1113// });
1114// executor.run_until_parked();
1115
1116// assert_eq!(
1117// pane_summaries(&workspace_a, cx_a),
1118// &[
1119// PaneSummary {
1120// active: false,
1121// leader: None,
1122// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1123// },
1124// PaneSummary {
1125// active: true,
1126// leader: None,
1127// items: vec![
1128// (false, "1.txt".into()),
1129// (true, "2.txt".into()),
1130// (false, "4.txt".into()),
1131// (false, "3.txt".into()),
1132// ]
1133// },
1134// ]
1135// );
1136// assert_eq!(
1137// pane_summaries(&workspace_b, cx_b),
1138// &[
1139// PaneSummary {
1140// active: false,
1141// leader: None,
1142// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1143// },
1144// PaneSummary {
1145// active: true,
1146// leader: client_a.peer_id(),
1147// items: vec![
1148// (false, "3.txt".into()),
1149// (false, "4.txt".into()),
1150// (true, "2.txt".into())
1151// ]
1152// },
1153// ]
1154// );
1155
1156// workspace_a.update(cx_a, |workspace, cx| {
1157// workspace.active_pane().update(cx, |pane, cx| {
1158// pane.activate_prev_item(true, cx);
1159// });
1160// });
1161// executor.run_until_parked();
1162
1163// assert_eq!(
1164// pane_summaries(&workspace_a, cx_a),
1165// &[
1166// PaneSummary {
1167// active: false,
1168// leader: None,
1169// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1170// },
1171// PaneSummary {
1172// active: true,
1173// leader: None,
1174// items: vec![
1175// (true, "1.txt".into()),
1176// (false, "2.txt".into()),
1177// (false, "4.txt".into()),
1178// (false, "3.txt".into()),
1179// ]
1180// },
1181// ]
1182// );
1183// assert_eq!(
1184// pane_summaries(&workspace_b, cx_b),
1185// &[
1186// PaneSummary {
1187// active: false,
1188// leader: None,
1189// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1190// },
1191// PaneSummary {
1192// active: true,
1193// leader: client_a.peer_id(),
1194// items: vec![
1195// (false, "3.txt".into()),
1196// (false, "4.txt".into()),
1197// (false, "2.txt".into()),
1198// (true, "1.txt".into()),
1199// ]
1200// },
1201// ]
1202// );
1203// }
1204
1205// #[gpui::test(iterations = 10)]
1206// async fn test_auto_unfollowing(
1207// executor: BackgroundExecutor,
1208// cx_a: &mut TestAppContext,
1209// cx_b: &mut TestAppContext,
1210// ) {
1211// // 2 clients connect to a server.
1212// let mut server = TestServer::start(executor.clone()).await;
1213// let client_a = server.create_client(cx_a, "user_a").await;
1214// let client_b = server.create_client(cx_b, "user_b").await;
1215// server
1216// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1217// .await;
1218// let active_call_a = cx_a.read(ActiveCall::global);
1219// let active_call_b = cx_b.read(ActiveCall::global);
1220
1221// cx_a.update(editor::init);
1222// cx_b.update(editor::init);
1223
1224// // Client A shares a project.
1225// client_a
1226// .fs()
1227// .insert_tree(
1228// "/a",
1229// json!({
1230// "1.txt": "one",
1231// "2.txt": "two",
1232// "3.txt": "three",
1233// }),
1234// )
1235// .await;
1236// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1237// active_call_a
1238// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1239// .await
1240// .unwrap();
1241
1242// let project_id = active_call_a
1243// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1244// .await
1245// .unwrap();
1246// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1247// active_call_b
1248// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1249// .await
1250// .unwrap();
1251
1252// todo!("could be wrong")
1253// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
1254// let cx_a = &mut cx_a;
1255// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
1256// let cx_b = &mut cx_b;
1257
1258// // Client A opens some editors.
1259// let workspace_a = client_a
1260// .build_workspace(&project_a, cx_a)
1261// .root(cx_a)
1262// .unwrap();
1263// let _editor_a1 = workspace_a
1264// .update(cx_a, |workspace, cx| {
1265// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
1266// })
1267// .await
1268// .unwrap()
1269// .downcast::<Editor>()
1270// .unwrap();
1271
1272// // Client B starts following client A.
1273// let workspace_b = client_b
1274// .build_workspace(&project_b, cx_b)
1275// .root(cx_b)
1276// .unwrap();
1277// let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
1278// let leader_id = project_b.update(cx_b, |project, _| {
1279// project.collaborators().values().next().unwrap().peer_id
1280// });
1281// workspace_b
1282// .update(cx_b, |workspace, cx| {
1283// workspace.follow(leader_id, cx).unwrap()
1284// })
1285// .await
1286// .unwrap();
1287// assert_eq!(
1288// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1289// Some(leader_id)
1290// );
1291// let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
1292// workspace
1293// .active_item(cx)
1294// .unwrap()
1295// .downcast::<Editor>()
1296// .unwrap()
1297// });
1298
1299// // When client B moves, it automatically stops following client A.
1300// editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
1301// assert_eq!(
1302// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1303// None
1304// );
1305
1306// workspace_b
1307// .update(cx_b, |workspace, cx| {
1308// workspace.follow(leader_id, cx).unwrap()
1309// })
1310// .await
1311// .unwrap();
1312// assert_eq!(
1313// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1314// Some(leader_id)
1315// );
1316
1317// // When client B edits, it automatically stops following client A.
1318// editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
1319// assert_eq!(
1320// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1321// None
1322// );
1323
1324// workspace_b
1325// .update(cx_b, |workspace, cx| {
1326// workspace.follow(leader_id, cx).unwrap()
1327// })
1328// .await
1329// .unwrap();
1330// assert_eq!(
1331// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1332// Some(leader_id)
1333// );
1334
1335// // When client B scrolls, it automatically stops following client A.
1336// editor_b2.update(cx_b, |editor, cx| {
1337// editor.set_scroll_position(point(0., 3.), cx)
1338// });
1339// assert_eq!(
1340// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1341// None
1342// );
1343
1344// workspace_b
1345// .update(cx_b, |workspace, cx| {
1346// workspace.follow(leader_id, cx).unwrap()
1347// })
1348// .await
1349// .unwrap();
1350// assert_eq!(
1351// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1352// Some(leader_id)
1353// );
1354
1355// // When client B activates a different pane, it continues following client A in the original pane.
1356// workspace_b.update(cx_b, |workspace, cx| {
1357// workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
1358// });
1359// assert_eq!(
1360// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1361// Some(leader_id)
1362// );
1363
1364// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
1365// assert_eq!(
1366// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1367// Some(leader_id)
1368// );
1369
1370// // When client B activates a different item in the original pane, it automatically stops following client A.
1371// workspace_b
1372// .update(cx_b, |workspace, cx| {
1373// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
1374// })
1375// .await
1376// .unwrap();
1377// assert_eq!(
1378// workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1379// None
1380// );
1381// }
1382
1383// #[gpui::test(iterations = 10)]
1384// async fn test_peers_simultaneously_following_each_other(
1385// executor: BackgroundExecutor,
1386// cx_a: &mut TestAppContext,
1387// cx_b: &mut TestAppContext,
1388// ) {
1389// let mut server = TestServer::start(executor.clone()).await;
1390// let client_a = server.create_client(cx_a, "user_a").await;
1391// let client_b = server.create_client(cx_b, "user_b").await;
1392// server
1393// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1394// .await;
1395// let active_call_a = cx_a.read(ActiveCall::global);
1396
1397// cx_a.update(editor::init);
1398// cx_b.update(editor::init);
1399
1400// client_a.fs().insert_tree("/a", json!({})).await;
1401// let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1402// let workspace_a = client_a
1403// .build_workspace(&project_a, cx_a)
1404// .root(cx_a)
1405// .unwrap();
1406// let project_id = active_call_a
1407// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1408// .await
1409// .unwrap();
1410
1411// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1412// let workspace_b = client_b
1413// .build_workspace(&project_b, cx_b)
1414// .root(cx_b)
1415// .unwrap();
1416
1417// executor.run_until_parked();
1418// let client_a_id = project_b.update(cx_b, |project, _| {
1419// project.collaborators().values().next().unwrap().peer_id
1420// });
1421// let client_b_id = project_a.update(cx_a, |project, _| {
1422// project.collaborators().values().next().unwrap().peer_id
1423// });
1424
1425// let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
1426// workspace.follow(client_b_id, cx).unwrap()
1427// });
1428// let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
1429// workspace.follow(client_a_id, cx).unwrap()
1430// });
1431
1432// futures::try_join!(a_follow_b, b_follow_a).unwrap();
1433// workspace_a.update(cx_a, |workspace, _| {
1434// assert_eq!(
1435// workspace.leader_for_pane(workspace.active_pane()),
1436// Some(client_b_id)
1437// );
1438// });
1439// workspace_b.update(cx_b, |workspace, _| {
1440// assert_eq!(
1441// workspace.leader_for_pane(workspace.active_pane()),
1442// Some(client_a_id)
1443// );
1444// });
1445// }
1446
1447// #[gpui::test(iterations = 10)]
1448// async fn test_following_across_workspaces(
1449// executor: BackgroundExecutor,
1450// cx_a: &mut TestAppContext,
1451// cx_b: &mut TestAppContext,
1452// ) {
1453// // a and b join a channel/call
1454// // a shares project 1
1455// // b shares project 2
1456// //
1457// // b follows a: causes project 2 to be joined, and b to follow a.
1458// // b opens a different file in project 2, a follows b
1459// // b opens a different file in project 1, a cannot follow b
1460// // b shares the project, a joins the project and follows b
1461// let mut server = TestServer::start(executor.clone()).await;
1462// let client_a = server.create_client(cx_a, "user_a").await;
1463// let client_b = server.create_client(cx_b, "user_b").await;
1464// cx_a.update(editor::init);
1465// cx_b.update(editor::init);
1466
1467// client_a
1468// .fs()
1469// .insert_tree(
1470// "/a",
1471// json!({
1472// "w.rs": "",
1473// "x.rs": "",
1474// }),
1475// )
1476// .await;
1477
1478// client_b
1479// .fs()
1480// .insert_tree(
1481// "/b",
1482// json!({
1483// "y.rs": "",
1484// "z.rs": "",
1485// }),
1486// )
1487// .await;
1488
1489// server
1490// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1491// .await;
1492// let active_call_a = cx_a.read(ActiveCall::global);
1493// let active_call_b = cx_b.read(ActiveCall::global);
1494
1495// let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
1496// let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
1497
1498// let workspace_a = client_a
1499// .build_workspace(&project_a, cx_a)
1500// .root(cx_a)
1501// .unwrap();
1502// let workspace_b = client_b
1503// .build_workspace(&project_b, cx_b)
1504// .root(cx_b)
1505// .unwrap();
1506
1507// cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
1508// cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
1509
1510// active_call_a
1511// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1512// .await
1513// .unwrap();
1514
1515// active_call_a
1516// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1517// .await
1518// .unwrap();
1519// active_call_b
1520// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1521// .await
1522// .unwrap();
1523
1524// todo!("could be wrong")
1525// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
1526// let cx_a = &mut cx_a;
1527// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
1528// let cx_b = &mut cx_b;
1529
1530// workspace_a
1531// .update(cx_a, |workspace, cx| {
1532// workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
1533// })
1534// .await
1535// .unwrap();
1536
1537// executor.run_until_parked();
1538// assert_eq!(visible_push_notifications(cx_b).len(), 1);
1539
1540// workspace_b.update(cx_b, |workspace, cx| {
1541// workspace
1542// .follow(client_a.peer_id().unwrap(), cx)
1543// .unwrap()
1544// .detach()
1545// });
1546
1547// executor.run_until_parked();
1548// let workspace_b_project_a = cx_b
1549// .windows()
1550// .iter()
1551// .max_by_key(|window| window.item_id())
1552// .unwrap()
1553// .downcast::<Workspace>()
1554// .unwrap()
1555// .root(cx_b)
1556// .unwrap();
1557
1558// // assert that b is following a in project a in w.rs
1559// workspace_b_project_a.update(cx_b, |workspace, cx| {
1560// assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
1561// assert_eq!(
1562// client_a.peer_id(),
1563// workspace.leader_for_pane(workspace.active_pane())
1564// );
1565// let item = workspace.active_item(cx).unwrap();
1566// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("w.rs"));
1567// });
1568
1569// // TODO: in app code, this would be done by the collab_ui.
1570// active_call_b
1571// .update(cx_b, |call, cx| {
1572// let project = workspace_b_project_a.read(cx).project().clone();
1573// call.set_location(Some(&project), cx)
1574// })
1575// .await
1576// .unwrap();
1577
1578// // assert that there are no share notifications open
1579// assert_eq!(visible_push_notifications(cx_b).len(), 0);
1580
1581// // b moves to x.rs in a's project, and a follows
1582// workspace_b_project_a
1583// .update(cx_b, |workspace, cx| {
1584// workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
1585// })
1586// .await
1587// .unwrap();
1588
1589// executor.run_until_parked();
1590// workspace_b_project_a.update(cx_b, |workspace, cx| {
1591// let item = workspace.active_item(cx).unwrap();
1592// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
1593// });
1594
1595// workspace_a.update(cx_a, |workspace, cx| {
1596// workspace
1597// .follow(client_b.peer_id().unwrap(), cx)
1598// .unwrap()
1599// .detach()
1600// });
1601
1602// executor.run_until_parked();
1603// workspace_a.update(cx_a, |workspace, cx| {
1604// assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
1605// assert_eq!(
1606// client_b.peer_id(),
1607// workspace.leader_for_pane(workspace.active_pane())
1608// );
1609// let item = workspace.active_pane().read(cx).active_item().unwrap();
1610// assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs".into());
1611// });
1612
1613// // b moves to y.rs in b's project, a is still following but can't yet see
1614// workspace_b
1615// .update(cx_b, |workspace, cx| {
1616// workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
1617// })
1618// .await
1619// .unwrap();
1620
1621// // TODO: in app code, this would be done by the collab_ui.
1622// active_call_b
1623// .update(cx_b, |call, cx| {
1624// let project = workspace_b.read(cx).project().clone();
1625// call.set_location(Some(&project), cx)
1626// })
1627// .await
1628// .unwrap();
1629
1630// let project_b_id = active_call_b
1631// .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1632// .await
1633// .unwrap();
1634
1635// executor.run_until_parked();
1636// assert_eq!(visible_push_notifications(cx_a).len(), 1);
1637// cx_a.update(|cx| {
1638// workspace::join_remote_project(
1639// project_b_id,
1640// client_b.user_id().unwrap(),
1641// client_a.app_state.clone(),
1642// cx,
1643// )
1644// })
1645// .await
1646// .unwrap();
1647
1648// executor.run_until_parked();
1649
1650// assert_eq!(visible_push_notifications(cx_a).len(), 0);
1651// let workspace_a_project_b = cx_a
1652// .windows()
1653// .iter()
1654// .max_by_key(|window| window.item_id())
1655// .unwrap()
1656// .downcast::<Workspace>()
1657// .unwrap()
1658// .root(cx_a)
1659// .unwrap();
1660
1661// workspace_a_project_b.update(cx_a, |workspace, cx| {
1662// assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
1663// assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
1664// assert_eq!(
1665// client_b.peer_id(),
1666// workspace.leader_for_pane(workspace.active_pane())
1667// );
1668// let item = workspace.active_item(cx).unwrap();
1669// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("y.rs"));
1670// });
1671// }
1672
1673// #[gpui::test]
1674// async fn test_following_into_excluded_file(
1675// executor: BackgroundExecutor,
1676// mut cx_a: &mut TestAppContext,
1677// mut cx_b: &mut TestAppContext,
1678// ) {
1679// let mut server = TestServer::start(executor.clone()).await;
1680// let client_a = server.create_client(cx_a, "user_a").await;
1681// let client_b = server.create_client(cx_b, "user_b").await;
1682// for cx in [&mut cx_a, &mut cx_b] {
1683// cx.update(|cx| {
1684// cx.update_global::<SettingsStore, _>(|store, cx| {
1685// store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
1686// project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
1687// });
1688// });
1689// });
1690// }
1691// server
1692// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1693// .await;
1694// let active_call_a = cx_a.read(ActiveCall::global);
1695// let active_call_b = cx_b.read(ActiveCall::global);
1696
1697// cx_a.update(editor::init);
1698// cx_b.update(editor::init);
1699
1700// client_a
1701// .fs()
1702// .insert_tree(
1703// "/a",
1704// json!({
1705// ".git": {
1706// "COMMIT_EDITMSG": "write your commit message here",
1707// },
1708// "1.txt": "one\none\none",
1709// "2.txt": "two\ntwo\ntwo",
1710// "3.txt": "three\nthree\nthree",
1711// }),
1712// )
1713// .await;
1714// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1715// active_call_a
1716// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1717// .await
1718// .unwrap();
1719
1720// let project_id = active_call_a
1721// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1722// .await
1723// .unwrap();
1724// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1725// active_call_b
1726// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1727// .await
1728// .unwrap();
1729
1730// let window_a = client_a.build_workspace(&project_a, cx_a);
1731// let workspace_a = window_a.root(cx_a).unwrap();
1732// let peer_id_a = client_a.peer_id().unwrap();
1733// let window_b = client_b.build_workspace(&project_b, cx_b);
1734// let workspace_b = window_b.root(cx_b).unwrap();
1735
1736// todo!("could be wrong")
1737// let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
1738// let cx_a = &mut cx_a;
1739// let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
1740// let cx_b = &mut cx_b;
1741
1742// // Client A opens editors for a regular file and an excluded file.
1743// let editor_for_regular = workspace_a
1744// .update(cx_a, |workspace, cx| {
1745// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
1746// })
1747// .await
1748// .unwrap()
1749// .downcast::<Editor>()
1750// .unwrap();
1751// let editor_for_excluded_a = workspace_a
1752// .update(cx_a, |workspace, cx| {
1753// workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
1754// })
1755// .await
1756// .unwrap()
1757// .downcast::<Editor>()
1758// .unwrap();
1759
1760// // Client A updates their selections in those editors
1761// editor_for_regular.update(cx_a, |editor, cx| {
1762// editor.handle_input("a", cx);
1763// editor.handle_input("b", cx);
1764// editor.handle_input("c", cx);
1765// editor.select_left(&Default::default(), cx);
1766// assert_eq!(editor.selections.ranges(cx), vec![3..2]);
1767// });
1768// editor_for_excluded_a.update(cx_a, |editor, cx| {
1769// editor.select_all(&Default::default(), cx);
1770// editor.handle_input("new commit message", cx);
1771// editor.select_left(&Default::default(), cx);
1772// assert_eq!(editor.selections.ranges(cx), vec![18..17]);
1773// });
1774
1775// // When client B starts following client A, currently visible file is replicated
1776// workspace_b
1777// .update(cx_b, |workspace, cx| {
1778// workspace.follow(peer_id_a, cx).unwrap()
1779// })
1780// .await
1781// .unwrap();
1782
1783// let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
1784// workspace
1785// .active_item(cx)
1786// .unwrap()
1787// .downcast::<Editor>()
1788// .unwrap()
1789// });
1790// assert_eq!(
1791// cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
1792// Some((worktree_id, ".git/COMMIT_EDITMSG").into())
1793// );
1794// assert_eq!(
1795// editor_for_excluded_b.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
1796// vec![18..17]
1797// );
1798
1799// // Changes from B to the excluded file are replicated in A's editor
1800// editor_for_excluded_b.update(cx_b, |editor, cx| {
1801// editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
1802// });
1803// executor.run_until_parked();
1804// editor_for_excluded_a.update(cx_a, |editor, cx| {
1805// assert_eq!(
1806// editor.text(cx),
1807// "new commit messag\nCo-Authored-By: B <b@b.b>"
1808// );
1809// });
1810// }
1811
1812// fn visible_push_notifications(
1813// cx: &mut TestAppContext,
1814// ) -> Vec<gpui::View<ProjectSharedNotification>> {
1815// let mut ret = Vec::new();
1816// for window in cx.windows() {
1817// window.update(cx, |window| {
1818// if let Some(handle) = window
1819// .root_view()
1820// .clone()
1821// .downcast::<ProjectSharedNotification>()
1822// {
1823// ret.push(handle)
1824// }
1825// });
1826// }
1827// ret
1828// }
1829
1830// #[derive(Debug, PartialEq, Eq)]
1831// struct PaneSummary {
1832// active: bool,
1833// leader: Option<PeerId>,
1834// items: Vec<(bool, String)>,
1835// }
1836
1837// fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec<PeerId>)> {
1838// cx.read(|cx| {
1839// let active_call = ActiveCall::global(cx).read(cx);
1840// let peer_id = active_call.client().peer_id();
1841// let room = active_call.room().unwrap().read(cx);
1842// let mut result = room
1843// .remote_participants()
1844// .values()
1845// .map(|participant| participant.peer_id)
1846// .chain(peer_id)
1847// .filter_map(|peer_id| {
1848// let followers = room.followers_for(peer_id, project_id);
1849// if followers.is_empty() {
1850// None
1851// } else {
1852// Some((peer_id, followers.to_vec()))
1853// }
1854// })
1855// .collect::<Vec<_>>();
1856// result.sort_by_key(|e| e.0);
1857// result
1858// })
1859// }
1860
1861// fn pane_summaries(workspace: &View<Workspace>, cx: &mut WindowContext<'_>) -> Vec<PaneSummary> {
1862// workspace.update(cx, |workspace, cx| {
1863// let active_pane = workspace.active_pane();
1864// workspace
1865// .panes()
1866// .iter()
1867// .map(|pane| {
1868// let leader = workspace.leader_for_pane(pane);
1869// let active = pane == active_pane;
1870// let pane = pane.read(cx);
1871// let active_ix = pane.active_item_index();
1872// PaneSummary {
1873// active,
1874// leader,
1875// items: pane
1876// .items()
1877// .enumerate()
1878// .map(|(ix, item)| {
1879// (
1880// ix == active_ix,
1881// item.tab_description(0, cx)
1882// .map_or(String::new(), |s| s.to_string()),
1883// )
1884// })
1885// .collect(),
1886// }
1887// })
1888// .collect()
1889// })
1890// }