1use crate::{
2 db::{self, NewUserParams, TestDb, UserId},
3 executor::Executor,
4 rpc::{Server, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
5 AppState,
6};
7use anyhow::anyhow;
8use call::{room, ActiveCall, ParticipantLocation, Room};
9use client::{
10 self, proto::PeerId, test::FakeHttpClient, Client, Connection, Credentials,
11 EstablishConnectionError, User, UserStore, RECEIVE_TIMEOUT,
12};
13use collections::{BTreeMap, HashMap, HashSet};
14use editor::{
15 self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, ExcerptRange, MultiBuffer,
16 Redo, Rename, ToOffset, ToggleCodeActions, Undo,
17};
18use fs::{FakeFs, Fs as _, HomeDir, LineEnding, RemoveOptions};
19use futures::{channel::oneshot, StreamExt as _};
20use gpui::{
21 executor::Deterministic, geometry::vector::vec2f, test::EmptyView, ModelHandle, Task,
22 TestAppContext, ViewHandle,
23};
24use language::{
25 range_to_lsp, tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
26 LanguageConfig, LanguageRegistry, OffsetRangeExt, Point, PointUtf16, Rope,
27};
28use live_kit_client::MacOSDisplay;
29use lsp::{self, FakeLanguageServer};
30use parking_lot::Mutex;
31use project::{search::SearchQuery, DiagnosticSummary, Project, ProjectPath, WorktreeId};
32use rand::prelude::*;
33use serde_json::json;
34use settings::{Formatter, Settings};
35use std::{
36 cell::{Cell, RefCell},
37 env, future, mem,
38 ops::Deref,
39 path::{Path, PathBuf},
40 rc::Rc,
41 sync::{
42 atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
43 Arc,
44 },
45};
46use theme::ThemeRegistry;
47use unindent::Unindent as _;
48use util::post_inc;
49use workspace::{item::Item, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace};
50
51#[ctor::ctor]
52fn init_logger() {
53 if std::env::var("RUST_LOG").is_ok() {
54 env_logger::init();
55 }
56}
57
58#[gpui::test(iterations = 10)]
59async fn test_basic_calls(
60 deterministic: Arc<Deterministic>,
61 cx_a: &mut TestAppContext,
62 cx_b: &mut TestAppContext,
63 cx_b2: &mut TestAppContext,
64 cx_c: &mut TestAppContext,
65) {
66 deterministic.forbid_parking();
67 let mut server = TestServer::start(&deterministic).await;
68
69 let client_a = server.create_client(cx_a, "user_a").await;
70 let client_b = server.create_client(cx_b, "user_b").await;
71 let client_c = server.create_client(cx_c, "user_c").await;
72 server
73 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
74 .await;
75
76 let active_call_a = cx_a.read(ActiveCall::global);
77 let active_call_b = cx_b.read(ActiveCall::global);
78 let active_call_c = cx_c.read(ActiveCall::global);
79
80 // Call user B from client A.
81 active_call_a
82 .update(cx_a, |call, cx| {
83 call.invite(client_b.user_id().unwrap(), None, cx)
84 })
85 .await
86 .unwrap();
87 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
88 deterministic.run_until_parked();
89 assert_eq!(
90 room_participants(&room_a, cx_a),
91 RoomParticipants {
92 remote: Default::default(),
93 pending: vec!["user_b".to_string()]
94 }
95 );
96
97 // User B receives the call.
98 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
99 let call_b = incoming_call_b.next().await.unwrap().unwrap();
100 assert_eq!(call_b.calling_user.github_login, "user_a");
101
102 // User B connects via another client and also receives a ring on the newly-connected client.
103 let _client_b2 = server.create_client(cx_b2, "user_b").await;
104 let active_call_b2 = cx_b2.read(ActiveCall::global);
105 let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
106 deterministic.run_until_parked();
107 let call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
108 assert_eq!(call_b2.calling_user.github_login, "user_a");
109
110 // User B joins the room using the first client.
111 active_call_b
112 .update(cx_b, |call, cx| call.accept_incoming(cx))
113 .await
114 .unwrap();
115 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
116 assert!(incoming_call_b.next().await.unwrap().is_none());
117
118 deterministic.run_until_parked();
119 assert_eq!(
120 room_participants(&room_a, cx_a),
121 RoomParticipants {
122 remote: vec!["user_b".to_string()],
123 pending: Default::default()
124 }
125 );
126 assert_eq!(
127 room_participants(&room_b, cx_b),
128 RoomParticipants {
129 remote: vec!["user_a".to_string()],
130 pending: Default::default()
131 }
132 );
133
134 // Call user C from client B.
135 let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
136 active_call_b
137 .update(cx_b, |call, cx| {
138 call.invite(client_c.user_id().unwrap(), None, cx)
139 })
140 .await
141 .unwrap();
142
143 deterministic.run_until_parked();
144 assert_eq!(
145 room_participants(&room_a, cx_a),
146 RoomParticipants {
147 remote: vec!["user_b".to_string()],
148 pending: vec!["user_c".to_string()]
149 }
150 );
151 assert_eq!(
152 room_participants(&room_b, cx_b),
153 RoomParticipants {
154 remote: vec!["user_a".to_string()],
155 pending: vec!["user_c".to_string()]
156 }
157 );
158
159 // User C receives the call, but declines it.
160 let call_c = incoming_call_c.next().await.unwrap().unwrap();
161 assert_eq!(call_c.calling_user.github_login, "user_b");
162 active_call_c.update(cx_c, |call, _| call.decline_incoming().unwrap());
163 assert!(incoming_call_c.next().await.unwrap().is_none());
164
165 deterministic.run_until_parked();
166 assert_eq!(
167 room_participants(&room_a, cx_a),
168 RoomParticipants {
169 remote: vec!["user_b".to_string()],
170 pending: Default::default()
171 }
172 );
173 assert_eq!(
174 room_participants(&room_b, cx_b),
175 RoomParticipants {
176 remote: vec!["user_a".to_string()],
177 pending: Default::default()
178 }
179 );
180
181 // User A shares their screen
182 let display = MacOSDisplay::new();
183 let events_b = active_call_events(cx_b);
184 active_call_a
185 .update(cx_a, |call, cx| {
186 call.room().unwrap().update(cx, |room, cx| {
187 room.set_display_sources(vec![display.clone()]);
188 room.share_screen(cx)
189 })
190 })
191 .await
192 .unwrap();
193
194 deterministic.run_until_parked();
195
196 assert_eq!(events_b.borrow().len(), 1);
197 let event = events_b.borrow().first().unwrap().clone();
198 if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event {
199 assert_eq!(participant_id, client_a.peer_id().unwrap());
200 room_b.read_with(cx_b, |room, _| {
201 assert_eq!(
202 room.remote_participants()[&client_a.user_id().unwrap()]
203 .tracks
204 .len(),
205 1
206 );
207 });
208 } else {
209 panic!("unexpected event")
210 }
211
212 // User A leaves the room.
213 active_call_a.update(cx_a, |call, cx| {
214 call.hang_up(cx).unwrap();
215 assert!(call.room().is_none());
216 });
217 deterministic.run_until_parked();
218 assert_eq!(
219 room_participants(&room_a, cx_a),
220 RoomParticipants {
221 remote: Default::default(),
222 pending: Default::default()
223 }
224 );
225 assert_eq!(
226 room_participants(&room_b, cx_b),
227 RoomParticipants {
228 remote: Default::default(),
229 pending: Default::default()
230 }
231 );
232
233 // User B gets disconnected from the LiveKit server, which causes them
234 // to automatically leave the room.
235 server
236 .test_live_kit_server
237 .disconnect_client(client_b.peer_id().unwrap().to_string())
238 .await;
239 active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
240 assert_eq!(
241 room_participants(&room_a, cx_a),
242 RoomParticipants {
243 remote: Default::default(),
244 pending: Default::default()
245 }
246 );
247 assert_eq!(
248 room_participants(&room_b, cx_b),
249 RoomParticipants {
250 remote: Default::default(),
251 pending: Default::default()
252 }
253 );
254}
255
256#[gpui::test(iterations = 10)]
257async fn test_room_uniqueness(
258 deterministic: Arc<Deterministic>,
259 cx_a: &mut TestAppContext,
260 cx_a2: &mut TestAppContext,
261 cx_b: &mut TestAppContext,
262 cx_b2: &mut TestAppContext,
263 cx_c: &mut TestAppContext,
264) {
265 deterministic.forbid_parking();
266 let mut server = TestServer::start(&deterministic).await;
267 let client_a = server.create_client(cx_a, "user_a").await;
268 let _client_a2 = server.create_client(cx_a2, "user_a").await;
269 let client_b = server.create_client(cx_b, "user_b").await;
270 let _client_b2 = server.create_client(cx_b2, "user_b").await;
271 let client_c = server.create_client(cx_c, "user_c").await;
272 server
273 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
274 .await;
275
276 let active_call_a = cx_a.read(ActiveCall::global);
277 let active_call_a2 = cx_a2.read(ActiveCall::global);
278 let active_call_b = cx_b.read(ActiveCall::global);
279 let active_call_b2 = cx_b2.read(ActiveCall::global);
280 let active_call_c = cx_c.read(ActiveCall::global);
281
282 // Call user B from client A.
283 active_call_a
284 .update(cx_a, |call, cx| {
285 call.invite(client_b.user_id().unwrap(), None, cx)
286 })
287 .await
288 .unwrap();
289
290 // Ensure a new room can't be created given user A just created one.
291 active_call_a2
292 .update(cx_a2, |call, cx| {
293 call.invite(client_c.user_id().unwrap(), None, cx)
294 })
295 .await
296 .unwrap_err();
297 active_call_a2.read_with(cx_a2, |call, _| assert!(call.room().is_none()));
298
299 // User B receives the call from user A.
300 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
301 let call_b1 = incoming_call_b.next().await.unwrap().unwrap();
302 assert_eq!(call_b1.calling_user.github_login, "user_a");
303
304 // Ensure calling users A and B from client C fails.
305 active_call_c
306 .update(cx_c, |call, cx| {
307 call.invite(client_a.user_id().unwrap(), None, cx)
308 })
309 .await
310 .unwrap_err();
311 active_call_c
312 .update(cx_c, |call, cx| {
313 call.invite(client_b.user_id().unwrap(), None, cx)
314 })
315 .await
316 .unwrap_err();
317
318 // Ensure User B can't create a room while they still have an incoming call.
319 active_call_b2
320 .update(cx_b2, |call, cx| {
321 call.invite(client_c.user_id().unwrap(), None, cx)
322 })
323 .await
324 .unwrap_err();
325 active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
326
327 // User B joins the room and calling them after they've joined still fails.
328 active_call_b
329 .update(cx_b, |call, cx| call.accept_incoming(cx))
330 .await
331 .unwrap();
332 active_call_c
333 .update(cx_c, |call, cx| {
334 call.invite(client_b.user_id().unwrap(), None, cx)
335 })
336 .await
337 .unwrap_err();
338
339 // Ensure User B can't create a room while they belong to another room.
340 active_call_b2
341 .update(cx_b2, |call, cx| {
342 call.invite(client_c.user_id().unwrap(), None, cx)
343 })
344 .await
345 .unwrap_err();
346 active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
347
348 // Client C can successfully call client B after client B leaves the room.
349 active_call_b
350 .update(cx_b, |call, cx| call.hang_up(cx))
351 .unwrap();
352 deterministic.run_until_parked();
353 active_call_c
354 .update(cx_c, |call, cx| {
355 call.invite(client_b.user_id().unwrap(), None, cx)
356 })
357 .await
358 .unwrap();
359 deterministic.run_until_parked();
360 let call_b2 = incoming_call_b.next().await.unwrap().unwrap();
361 assert_eq!(call_b2.calling_user.github_login, "user_c");
362}
363
364#[gpui::test(iterations = 10)]
365async fn test_client_disconnecting_from_room(
366 deterministic: Arc<Deterministic>,
367 cx_a: &mut TestAppContext,
368 cx_b: &mut TestAppContext,
369) {
370 deterministic.forbid_parking();
371 let mut server = TestServer::start(&deterministic).await;
372 let client_a = server.create_client(cx_a, "user_a").await;
373 let client_b = server.create_client(cx_b, "user_b").await;
374 server
375 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
376 .await;
377
378 let active_call_a = cx_a.read(ActiveCall::global);
379 let active_call_b = cx_b.read(ActiveCall::global);
380
381 // Call user B from client A.
382 active_call_a
383 .update(cx_a, |call, cx| {
384 call.invite(client_b.user_id().unwrap(), None, cx)
385 })
386 .await
387 .unwrap();
388 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
389
390 // User B receives the call and joins the room.
391 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
392 incoming_call_b.next().await.unwrap().unwrap();
393 active_call_b
394 .update(cx_b, |call, cx| call.accept_incoming(cx))
395 .await
396 .unwrap();
397 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
398 deterministic.run_until_parked();
399 assert_eq!(
400 room_participants(&room_a, cx_a),
401 RoomParticipants {
402 remote: vec!["user_b".to_string()],
403 pending: Default::default()
404 }
405 );
406 assert_eq!(
407 room_participants(&room_b, cx_b),
408 RoomParticipants {
409 remote: vec!["user_a".to_string()],
410 pending: Default::default()
411 }
412 );
413
414 // User A automatically reconnects to the room upon disconnection.
415 server.disconnect_client(client_a.peer_id().unwrap());
416 deterministic.advance_clock(RECEIVE_TIMEOUT);
417 deterministic.run_until_parked();
418 assert_eq!(
419 room_participants(&room_a, cx_a),
420 RoomParticipants {
421 remote: vec!["user_b".to_string()],
422 pending: Default::default()
423 }
424 );
425 assert_eq!(
426 room_participants(&room_b, cx_b),
427 RoomParticipants {
428 remote: vec!["user_a".to_string()],
429 pending: Default::default()
430 }
431 );
432
433 // When user A disconnects, both client A and B clear their room on the active call.
434 server.forbid_connections();
435 server.disconnect_client(client_a.peer_id().unwrap());
436 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
437 active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
438 active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
439 assert_eq!(
440 room_participants(&room_a, cx_a),
441 RoomParticipants {
442 remote: Default::default(),
443 pending: Default::default()
444 }
445 );
446 assert_eq!(
447 room_participants(&room_b, cx_b),
448 RoomParticipants {
449 remote: Default::default(),
450 pending: Default::default()
451 }
452 );
453
454 // Allow user A to reconnect to the server.
455 server.allow_connections();
456 deterministic.advance_clock(RECEIVE_TIMEOUT);
457
458 // Call user B again from client A.
459 active_call_a
460 .update(cx_a, |call, cx| {
461 call.invite(client_b.user_id().unwrap(), None, cx)
462 })
463 .await
464 .unwrap();
465 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
466
467 // User B receives the call and joins the room.
468 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
469 incoming_call_b.next().await.unwrap().unwrap();
470 active_call_b
471 .update(cx_b, |call, cx| call.accept_incoming(cx))
472 .await
473 .unwrap();
474 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
475 deterministic.run_until_parked();
476 assert_eq!(
477 room_participants(&room_a, cx_a),
478 RoomParticipants {
479 remote: vec!["user_b".to_string()],
480 pending: Default::default()
481 }
482 );
483 assert_eq!(
484 room_participants(&room_b, cx_b),
485 RoomParticipants {
486 remote: vec!["user_a".to_string()],
487 pending: Default::default()
488 }
489 );
490
491 // User B gets disconnected from the LiveKit server, which causes it
492 // to automatically leave the room.
493 server
494 .test_live_kit_server
495 .disconnect_client(client_b.user_id().unwrap().to_string())
496 .await;
497 deterministic.run_until_parked();
498 active_call_a.update(cx_a, |call, _| assert!(call.room().is_none()));
499 active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
500 assert_eq!(
501 room_participants(&room_a, cx_a),
502 RoomParticipants {
503 remote: Default::default(),
504 pending: Default::default()
505 }
506 );
507 assert_eq!(
508 room_participants(&room_b, cx_b),
509 RoomParticipants {
510 remote: Default::default(),
511 pending: Default::default()
512 }
513 );
514}
515
516#[gpui::test(iterations = 10)]
517async fn test_server_restarts(
518 deterministic: Arc<Deterministic>,
519 cx_a: &mut TestAppContext,
520 cx_b: &mut TestAppContext,
521 cx_c: &mut TestAppContext,
522 cx_d: &mut TestAppContext,
523) {
524 deterministic.forbid_parking();
525 let mut server = TestServer::start(&deterministic).await;
526 let client_a = server.create_client(cx_a, "user_a").await;
527 let client_b = server.create_client(cx_b, "user_b").await;
528 let client_c = server.create_client(cx_c, "user_c").await;
529 let client_d = server.create_client(cx_d, "user_d").await;
530 server
531 .make_contacts(&mut [
532 (&client_a, cx_a),
533 (&client_b, cx_b),
534 (&client_c, cx_c),
535 (&client_d, cx_d),
536 ])
537 .await;
538
539 let active_call_a = cx_a.read(ActiveCall::global);
540 let active_call_b = cx_b.read(ActiveCall::global);
541 let active_call_c = cx_c.read(ActiveCall::global);
542 let active_call_d = cx_d.read(ActiveCall::global);
543
544 // User A calls users B, C, and D.
545 active_call_a
546 .update(cx_a, |call, cx| {
547 call.invite(client_b.user_id().unwrap(), None, cx)
548 })
549 .await
550 .unwrap();
551 active_call_a
552 .update(cx_a, |call, cx| {
553 call.invite(client_c.user_id().unwrap(), None, cx)
554 })
555 .await
556 .unwrap();
557 active_call_a
558 .update(cx_a, |call, cx| {
559 call.invite(client_d.user_id().unwrap(), None, cx)
560 })
561 .await
562 .unwrap();
563 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
564
565 // User B receives the call and joins the room.
566 let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
567 assert!(incoming_call_b.next().await.unwrap().is_some());
568 active_call_b
569 .update(cx_b, |call, cx| call.accept_incoming(cx))
570 .await
571 .unwrap();
572 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
573
574 // User C receives the call and joins the room.
575 let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
576 assert!(incoming_call_c.next().await.unwrap().is_some());
577 active_call_c
578 .update(cx_c, |call, cx| call.accept_incoming(cx))
579 .await
580 .unwrap();
581 let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
582
583 // User D receives the call but doesn't join the room yet.
584 let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
585 assert!(incoming_call_d.next().await.unwrap().is_some());
586
587 deterministic.run_until_parked();
588 assert_eq!(
589 room_participants(&room_a, cx_a),
590 RoomParticipants {
591 remote: vec!["user_b".to_string(), "user_c".to_string()],
592 pending: vec!["user_d".to_string()]
593 }
594 );
595 assert_eq!(
596 room_participants(&room_b, cx_b),
597 RoomParticipants {
598 remote: vec!["user_a".to_string(), "user_c".to_string()],
599 pending: vec!["user_d".to_string()]
600 }
601 );
602 assert_eq!(
603 room_participants(&room_c, cx_c),
604 RoomParticipants {
605 remote: vec!["user_a".to_string(), "user_b".to_string()],
606 pending: vec!["user_d".to_string()]
607 }
608 );
609
610 // The server is torn down.
611 server.reset().await;
612
613 // Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
614 client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
615 deterministic.advance_clock(RECEIVE_TIMEOUT);
616 assert_eq!(
617 room_participants(&room_a, cx_a),
618 RoomParticipants {
619 remote: vec!["user_b".to_string(), "user_c".to_string()],
620 pending: vec!["user_d".to_string()]
621 }
622 );
623 assert_eq!(
624 room_participants(&room_b, cx_b),
625 RoomParticipants {
626 remote: vec!["user_a".to_string(), "user_c".to_string()],
627 pending: vec!["user_d".to_string()]
628 }
629 );
630 assert_eq!(
631 room_participants(&room_c, cx_c),
632 RoomParticipants {
633 remote: vec![],
634 pending: vec![]
635 }
636 );
637
638 // User D is notified again of the incoming call and accepts it.
639 assert!(incoming_call_d.next().await.unwrap().is_some());
640 active_call_d
641 .update(cx_d, |call, cx| call.accept_incoming(cx))
642 .await
643 .unwrap();
644 deterministic.run_until_parked();
645 let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
646 assert_eq!(
647 room_participants(&room_a, cx_a),
648 RoomParticipants {
649 remote: vec![
650 "user_b".to_string(),
651 "user_c".to_string(),
652 "user_d".to_string(),
653 ],
654 pending: vec![]
655 }
656 );
657 assert_eq!(
658 room_participants(&room_b, cx_b),
659 RoomParticipants {
660 remote: vec![
661 "user_a".to_string(),
662 "user_c".to_string(),
663 "user_d".to_string(),
664 ],
665 pending: vec![]
666 }
667 );
668 assert_eq!(
669 room_participants(&room_c, cx_c),
670 RoomParticipants {
671 remote: vec![],
672 pending: vec![]
673 }
674 );
675 assert_eq!(
676 room_participants(&room_d, cx_d),
677 RoomParticipants {
678 remote: vec![
679 "user_a".to_string(),
680 "user_b".to_string(),
681 "user_c".to_string(),
682 ],
683 pending: vec![]
684 }
685 );
686
687 // The server finishes restarting, cleaning up stale connections.
688 server.start().await.unwrap();
689 deterministic.advance_clock(CLEANUP_TIMEOUT);
690 assert_eq!(
691 room_participants(&room_a, cx_a),
692 RoomParticipants {
693 remote: vec!["user_b".to_string(), "user_d".to_string()],
694 pending: vec![]
695 }
696 );
697 assert_eq!(
698 room_participants(&room_b, cx_b),
699 RoomParticipants {
700 remote: vec!["user_a".to_string(), "user_d".to_string()],
701 pending: vec![]
702 }
703 );
704 assert_eq!(
705 room_participants(&room_c, cx_c),
706 RoomParticipants {
707 remote: vec![],
708 pending: vec![]
709 }
710 );
711 assert_eq!(
712 room_participants(&room_d, cx_d),
713 RoomParticipants {
714 remote: vec!["user_a".to_string(), "user_b".to_string()],
715 pending: vec![]
716 }
717 );
718
719 // User D hangs up.
720 active_call_d
721 .update(cx_d, |call, cx| call.hang_up(cx))
722 .unwrap();
723 deterministic.run_until_parked();
724 assert_eq!(
725 room_participants(&room_a, cx_a),
726 RoomParticipants {
727 remote: vec!["user_b".to_string()],
728 pending: vec![]
729 }
730 );
731 assert_eq!(
732 room_participants(&room_b, cx_b),
733 RoomParticipants {
734 remote: vec!["user_a".to_string()],
735 pending: vec![]
736 }
737 );
738 assert_eq!(
739 room_participants(&room_c, cx_c),
740 RoomParticipants {
741 remote: vec![],
742 pending: vec![]
743 }
744 );
745 assert_eq!(
746 room_participants(&room_d, cx_d),
747 RoomParticipants {
748 remote: vec![],
749 pending: vec![]
750 }
751 );
752
753 // User B calls user D again.
754 active_call_b
755 .update(cx_b, |call, cx| {
756 call.invite(client_d.user_id().unwrap(), None, cx)
757 })
758 .await
759 .unwrap();
760
761 // User D receives the call but doesn't join the room yet.
762 let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
763 assert!(incoming_call_d.next().await.unwrap().is_some());
764 deterministic.run_until_parked();
765 assert_eq!(
766 room_participants(&room_a, cx_a),
767 RoomParticipants {
768 remote: vec!["user_b".to_string()],
769 pending: vec!["user_d".to_string()]
770 }
771 );
772 assert_eq!(
773 room_participants(&room_b, cx_b),
774 RoomParticipants {
775 remote: vec!["user_a".to_string()],
776 pending: vec!["user_d".to_string()]
777 }
778 );
779
780 // The server is torn down.
781 server.reset().await;
782
783 // Users A and B have troubles reconnecting, so they leave the room.
784 client_a.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
785 client_b.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
786 client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
787 deterministic.advance_clock(RECEIVE_TIMEOUT);
788 assert_eq!(
789 room_participants(&room_a, cx_a),
790 RoomParticipants {
791 remote: vec![],
792 pending: vec![]
793 }
794 );
795 assert_eq!(
796 room_participants(&room_b, cx_b),
797 RoomParticipants {
798 remote: vec![],
799 pending: vec![]
800 }
801 );
802
803 // User D is notified again of the incoming call but doesn't accept it.
804 assert!(incoming_call_d.next().await.unwrap().is_some());
805
806 // The server finishes restarting, cleaning up stale connections and canceling the
807 // call to user D because the room has become empty.
808 server.start().await.unwrap();
809 deterministic.advance_clock(CLEANUP_TIMEOUT);
810 assert!(incoming_call_d.next().await.unwrap().is_none());
811}
812
813#[gpui::test(iterations = 10)]
814async fn test_calls_on_multiple_connections(
815 deterministic: Arc<Deterministic>,
816 cx_a: &mut TestAppContext,
817 cx_b1: &mut TestAppContext,
818 cx_b2: &mut TestAppContext,
819) {
820 deterministic.forbid_parking();
821 let mut server = TestServer::start(&deterministic).await;
822 let client_a = server.create_client(cx_a, "user_a").await;
823 let client_b1 = server.create_client(cx_b1, "user_b").await;
824 let client_b2 = server.create_client(cx_b2, "user_b").await;
825 server
826 .make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)])
827 .await;
828
829 let active_call_a = cx_a.read(ActiveCall::global);
830 let active_call_b1 = cx_b1.read(ActiveCall::global);
831 let active_call_b2 = cx_b2.read(ActiveCall::global);
832 let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
833 let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
834 assert!(incoming_call_b1.next().await.unwrap().is_none());
835 assert!(incoming_call_b2.next().await.unwrap().is_none());
836
837 // Call user B from client A, ensuring both clients for user B ring.
838 active_call_a
839 .update(cx_a, |call, cx| {
840 call.invite(client_b1.user_id().unwrap(), None, cx)
841 })
842 .await
843 .unwrap();
844 deterministic.run_until_parked();
845 assert!(incoming_call_b1.next().await.unwrap().is_some());
846 assert!(incoming_call_b2.next().await.unwrap().is_some());
847
848 // User B declines the call on one of the two connections, causing both connections
849 // to stop ringing.
850 active_call_b2.update(cx_b2, |call, _| call.decline_incoming().unwrap());
851 deterministic.run_until_parked();
852 assert!(incoming_call_b1.next().await.unwrap().is_none());
853 assert!(incoming_call_b2.next().await.unwrap().is_none());
854
855 // Call user B again from client A.
856 active_call_a
857 .update(cx_a, |call, cx| {
858 call.invite(client_b1.user_id().unwrap(), None, cx)
859 })
860 .await
861 .unwrap();
862 deterministic.run_until_parked();
863 assert!(incoming_call_b1.next().await.unwrap().is_some());
864 assert!(incoming_call_b2.next().await.unwrap().is_some());
865
866 // User B accepts the call on one of the two connections, causing both connections
867 // to stop ringing.
868 active_call_b2
869 .update(cx_b2, |call, cx| call.accept_incoming(cx))
870 .await
871 .unwrap();
872 deterministic.run_until_parked();
873 assert!(incoming_call_b1.next().await.unwrap().is_none());
874 assert!(incoming_call_b2.next().await.unwrap().is_none());
875
876 // User B disconnects the client that is not on the call. Everything should be fine.
877 client_b1.disconnect(&cx_b1.to_async()).unwrap();
878 deterministic.advance_clock(RECEIVE_TIMEOUT);
879 client_b1
880 .authenticate_and_connect(false, &cx_b1.to_async())
881 .await
882 .unwrap();
883
884 // User B hangs up, and user A calls them again.
885 active_call_b2.update(cx_b2, |call, cx| call.hang_up(cx).unwrap());
886 deterministic.run_until_parked();
887 active_call_a
888 .update(cx_a, |call, cx| {
889 call.invite(client_b1.user_id().unwrap(), None, cx)
890 })
891 .await
892 .unwrap();
893 deterministic.run_until_parked();
894 assert!(incoming_call_b1.next().await.unwrap().is_some());
895 assert!(incoming_call_b2.next().await.unwrap().is_some());
896
897 // User A cancels the call, causing both connections to stop ringing.
898 active_call_a
899 .update(cx_a, |call, cx| {
900 call.cancel_invite(client_b1.user_id().unwrap(), cx)
901 })
902 .await
903 .unwrap();
904 deterministic.run_until_parked();
905 assert!(incoming_call_b1.next().await.unwrap().is_none());
906 assert!(incoming_call_b2.next().await.unwrap().is_none());
907
908 // User A calls user B again.
909 active_call_a
910 .update(cx_a, |call, cx| {
911 call.invite(client_b1.user_id().unwrap(), None, cx)
912 })
913 .await
914 .unwrap();
915 deterministic.run_until_parked();
916 assert!(incoming_call_b1.next().await.unwrap().is_some());
917 assert!(incoming_call_b2.next().await.unwrap().is_some());
918
919 // User A hangs up, causing both connections to stop ringing.
920 active_call_a.update(cx_a, |call, cx| call.hang_up(cx).unwrap());
921 deterministic.run_until_parked();
922 assert!(incoming_call_b1.next().await.unwrap().is_none());
923 assert!(incoming_call_b2.next().await.unwrap().is_none());
924
925 // User A calls user B again.
926 active_call_a
927 .update(cx_a, |call, cx| {
928 call.invite(client_b1.user_id().unwrap(), None, cx)
929 })
930 .await
931 .unwrap();
932 deterministic.run_until_parked();
933 assert!(incoming_call_b1.next().await.unwrap().is_some());
934 assert!(incoming_call_b2.next().await.unwrap().is_some());
935
936 // User A disconnects, causing both connections to stop ringing.
937 server.forbid_connections();
938 server.disconnect_client(client_a.peer_id().unwrap());
939 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
940 assert!(incoming_call_b1.next().await.unwrap().is_none());
941 assert!(incoming_call_b2.next().await.unwrap().is_none());
942
943 // User A reconnects automatically, then calls user B again.
944 server.allow_connections();
945 deterministic.advance_clock(RECEIVE_TIMEOUT);
946 active_call_a
947 .update(cx_a, |call, cx| {
948 call.invite(client_b1.user_id().unwrap(), None, cx)
949 })
950 .await
951 .unwrap();
952 deterministic.run_until_parked();
953 assert!(incoming_call_b1.next().await.unwrap().is_some());
954 assert!(incoming_call_b2.next().await.unwrap().is_some());
955
956 // User B disconnects all clients, causing user A to no longer see a pending call for them.
957 server.forbid_connections();
958 server.disconnect_client(client_b1.peer_id().unwrap());
959 server.disconnect_client(client_b2.peer_id().unwrap());
960 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
961 active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
962}
963
964#[gpui::test(iterations = 10)]
965async fn test_share_project(
966 deterministic: Arc<Deterministic>,
967 cx_a: &mut TestAppContext,
968 cx_b: &mut TestAppContext,
969 cx_c: &mut TestAppContext,
970) {
971 deterministic.forbid_parking();
972 let (_, window_b) = cx_b.add_window(|_| EmptyView);
973 let mut server = TestServer::start(&deterministic).await;
974 let client_a = server.create_client(cx_a, "user_a").await;
975 let client_b = server.create_client(cx_b, "user_b").await;
976 let client_c = server.create_client(cx_c, "user_c").await;
977 server
978 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
979 .await;
980 let active_call_a = cx_a.read(ActiveCall::global);
981 let active_call_b = cx_b.read(ActiveCall::global);
982 let active_call_c = cx_c.read(ActiveCall::global);
983
984 client_a
985 .fs
986 .insert_tree(
987 "/a",
988 json!({
989 ".gitignore": "ignored-dir",
990 "a.txt": "a-contents",
991 "b.txt": "b-contents",
992 "ignored-dir": {
993 "c.txt": "",
994 "d.txt": "",
995 }
996 }),
997 )
998 .await;
999
1000 // Invite client B to collaborate on a project
1001 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1002 active_call_a
1003 .update(cx_a, |call, cx| {
1004 call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1005 })
1006 .await
1007 .unwrap();
1008
1009 // Join that project as client B
1010 let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1011 deterministic.run_until_parked();
1012 let call = incoming_call_b.borrow().clone().unwrap();
1013 assert_eq!(call.calling_user.github_login, "user_a");
1014 let initial_project = call.initial_project.unwrap();
1015 active_call_b
1016 .update(cx_b, |call, cx| call.accept_incoming(cx))
1017 .await
1018 .unwrap();
1019 let client_b_peer_id = client_b.peer_id().unwrap();
1020 let project_b = client_b
1021 .build_remote_project(initial_project.id, cx_b)
1022 .await;
1023 let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1024
1025 deterministic.run_until_parked();
1026 project_a.read_with(cx_a, |project, _| {
1027 let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1028 assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1029 });
1030 project_b.read_with(cx_b, |project, cx| {
1031 let worktree = project.worktrees(cx).next().unwrap().read(cx);
1032 assert_eq!(
1033 worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1034 [
1035 Path::new(".gitignore"),
1036 Path::new("a.txt"),
1037 Path::new("b.txt"),
1038 Path::new("ignored-dir"),
1039 Path::new("ignored-dir/c.txt"),
1040 Path::new("ignored-dir/d.txt"),
1041 ]
1042 );
1043 });
1044
1045 // Open the same file as client B and client A.
1046 let buffer_b = project_b
1047 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1048 .await
1049 .unwrap();
1050 buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1051 project_a.read_with(cx_a, |project, cx| {
1052 assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1053 });
1054 let buffer_a = project_a
1055 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1056 .await
1057 .unwrap();
1058
1059 let editor_b = cx_b.add_view(&window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
1060
1061 // Client A sees client B's selection
1062 deterministic.run_until_parked();
1063 buffer_a.read_with(cx_a, |buffer, _| {
1064 buffer
1065 .snapshot()
1066 .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1067 .count()
1068 == 1
1069 });
1070
1071 // Edit the buffer as client B and see that edit as client A.
1072 editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
1073 deterministic.run_until_parked();
1074 buffer_a.read_with(cx_a, |buffer, _| {
1075 assert_eq!(buffer.text(), "ok, b-contents")
1076 });
1077
1078 // Client B can invite client C on a project shared by client A.
1079 active_call_b
1080 .update(cx_b, |call, cx| {
1081 call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1082 })
1083 .await
1084 .unwrap();
1085
1086 let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1087 deterministic.run_until_parked();
1088 let call = incoming_call_c.borrow().clone().unwrap();
1089 assert_eq!(call.calling_user.github_login, "user_b");
1090 let initial_project = call.initial_project.unwrap();
1091 active_call_c
1092 .update(cx_c, |call, cx| call.accept_incoming(cx))
1093 .await
1094 .unwrap();
1095 let _project_c = client_c
1096 .build_remote_project(initial_project.id, cx_c)
1097 .await;
1098
1099 // Client B closes the editor, and client A sees client B's selections removed.
1100 cx_b.update(move |_| drop(editor_b));
1101 deterministic.run_until_parked();
1102 buffer_a.read_with(cx_a, |buffer, _| {
1103 buffer
1104 .snapshot()
1105 .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1106 .count()
1107 == 0
1108 });
1109}
1110
1111#[gpui::test(iterations = 10)]
1112async fn test_unshare_project(
1113 deterministic: Arc<Deterministic>,
1114 cx_a: &mut TestAppContext,
1115 cx_b: &mut TestAppContext,
1116 cx_c: &mut TestAppContext,
1117) {
1118 deterministic.forbid_parking();
1119 let mut server = TestServer::start(&deterministic).await;
1120 let client_a = server.create_client(cx_a, "user_a").await;
1121 let client_b = server.create_client(cx_b, "user_b").await;
1122 let client_c = server.create_client(cx_c, "user_c").await;
1123 server
1124 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1125 .await;
1126
1127 let active_call_a = cx_a.read(ActiveCall::global);
1128 let active_call_b = cx_b.read(ActiveCall::global);
1129
1130 client_a
1131 .fs
1132 .insert_tree(
1133 "/a",
1134 json!({
1135 "a.txt": "a-contents",
1136 "b.txt": "b-contents",
1137 }),
1138 )
1139 .await;
1140
1141 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1142 let project_id = active_call_a
1143 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1144 .await
1145 .unwrap();
1146 let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1147 let project_b = client_b.build_remote_project(project_id, cx_b).await;
1148 assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
1149
1150 project_b
1151 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1152 .await
1153 .unwrap();
1154
1155 // When client B leaves the room, the project becomes read-only.
1156 active_call_b.update(cx_b, |call, cx| call.hang_up(cx).unwrap());
1157 deterministic.run_until_parked();
1158 assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
1159
1160 // Client C opens the project.
1161 let project_c = client_c.build_remote_project(project_id, cx_c).await;
1162
1163 // When client A unshares the project, client C's project becomes read-only.
1164 project_a
1165 .update(cx_a, |project, cx| project.unshare(cx))
1166 .unwrap();
1167 deterministic.run_until_parked();
1168 assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
1169 assert!(project_c.read_with(cx_c, |project, _| project.is_read_only()));
1170
1171 // Client C can open the project again after client A re-shares.
1172 let project_id = active_call_a
1173 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1174 .await
1175 .unwrap();
1176 let project_c2 = client_c.build_remote_project(project_id, cx_c).await;
1177 assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
1178 project_c2
1179 .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1180 .await
1181 .unwrap();
1182
1183 // When client A (the host) leaves the room, the project gets unshared and guests are notified.
1184 active_call_a.update(cx_a, |call, cx| call.hang_up(cx).unwrap());
1185 deterministic.run_until_parked();
1186 project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1187 project_c2.read_with(cx_c, |project, _| {
1188 assert!(project.is_read_only());
1189 assert!(project.collaborators().is_empty());
1190 });
1191}
1192
1193#[gpui::test(iterations = 10)]
1194async fn test_host_disconnect(
1195 deterministic: Arc<Deterministic>,
1196 cx_a: &mut TestAppContext,
1197 cx_b: &mut TestAppContext,
1198 cx_c: &mut TestAppContext,
1199) {
1200 cx_b.update(editor::init);
1201 deterministic.forbid_parking();
1202 let mut server = TestServer::start(&deterministic).await;
1203 let client_a = server.create_client(cx_a, "user_a").await;
1204 let client_b = server.create_client(cx_b, "user_b").await;
1205 let client_c = server.create_client(cx_c, "user_c").await;
1206 server
1207 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1208 .await;
1209
1210 client_a
1211 .fs
1212 .insert_tree(
1213 "/a",
1214 json!({
1215 "a.txt": "a-contents",
1216 "b.txt": "b-contents",
1217 }),
1218 )
1219 .await;
1220
1221 let active_call_a = cx_a.read(ActiveCall::global);
1222 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1223 let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1224 let project_id = active_call_a
1225 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1226 .await
1227 .unwrap();
1228
1229 let project_b = client_b.build_remote_project(project_id, cx_b).await;
1230 assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
1231
1232 let (_, workspace_b) = cx_b.add_window(|cx| {
1233 Workspace::new(
1234 Default::default(),
1235 0,
1236 project_b.clone(),
1237 |_, _| unimplemented!(),
1238 cx,
1239 )
1240 });
1241 let editor_b = workspace_b
1242 .update(cx_b, |workspace, cx| {
1243 workspace.open_path((worktree_id, "b.txt"), None, true, cx)
1244 })
1245 .await
1246 .unwrap()
1247 .downcast::<Editor>()
1248 .unwrap();
1249 cx_b.read(|cx| {
1250 assert_eq!(
1251 cx.focused_view_id(workspace_b.window_id()),
1252 Some(editor_b.id())
1253 );
1254 });
1255 editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
1256 assert!(cx_b.is_window_edited(workspace_b.window_id()));
1257
1258 // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
1259 server.forbid_connections();
1260 server.disconnect_client(client_a.peer_id().unwrap());
1261 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1262 project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
1263 project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1264 project_b.read_with(cx_b, |project, _| project.is_read_only());
1265 assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
1266
1267 // Ensure client B's edited state is reset and that the whole window is blurred.
1268 cx_b.read(|cx| {
1269 assert_eq!(cx.focused_view_id(workspace_b.window_id()), None);
1270 });
1271 assert!(!cx_b.is_window_edited(workspace_b.window_id()));
1272
1273 // Ensure client B is not prompted to save edits when closing window after disconnecting.
1274 let can_close = workspace_b
1275 .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
1276 .await
1277 .unwrap();
1278 assert!(can_close);
1279
1280 // Allow client A to reconnect to the server.
1281 server.allow_connections();
1282 deterministic.advance_clock(RECEIVE_TIMEOUT);
1283
1284 // Client B calls client A again after they reconnected.
1285 let active_call_b = cx_b.read(ActiveCall::global);
1286 active_call_b
1287 .update(cx_b, |call, cx| {
1288 call.invite(client_a.user_id().unwrap(), None, cx)
1289 })
1290 .await
1291 .unwrap();
1292 deterministic.run_until_parked();
1293 active_call_a
1294 .update(cx_a, |call, cx| call.accept_incoming(cx))
1295 .await
1296 .unwrap();
1297
1298 active_call_a
1299 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1300 .await
1301 .unwrap();
1302
1303 // Drop client A's connection again. We should still unshare it successfully.
1304 server.disconnect_client(client_a.peer_id().unwrap());
1305 deterministic.advance_clock(RECEIVE_TIMEOUT);
1306 project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1307}
1308
1309#[gpui::test(iterations = 10)]
1310async fn test_host_reconnect(
1311 deterministic: Arc<Deterministic>,
1312 cx_a: &mut TestAppContext,
1313 cx_b: &mut TestAppContext,
1314) {
1315 cx_b.update(editor::init);
1316 deterministic.forbid_parking();
1317 let mut server = TestServer::start(&deterministic).await;
1318 let client_a = server.create_client(cx_a, "user_a").await;
1319 let client_b = server.create_client(cx_b, "user_b").await;
1320 server
1321 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1322 .await;
1323
1324 client_a
1325 .fs
1326 .insert_tree(
1327 "/root",
1328 json!({
1329 "dir1": {
1330 "a.txt": "a-contents",
1331 "b.txt": "b-contents",
1332 "subdir1": {
1333 "c.txt": "c-contents",
1334 "d.txt": "d-contents",
1335 "e.txt": "e-contents",
1336 }
1337 },
1338 "dir2": {
1339 "x.txt": "x-contents",
1340 },
1341 }),
1342 )
1343 .await;
1344
1345 let active_call_a = cx_a.read(ActiveCall::global);
1346 let (project_a, _) = client_a.build_local_project("/root/dir1", cx_a).await;
1347 let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1348 let project_id = active_call_a
1349 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1350 .await
1351 .unwrap();
1352
1353 let project_b = client_b.build_remote_project(project_id, cx_b).await;
1354 deterministic.run_until_parked();
1355
1356 let worktree_id = worktree_a.read_with(cx_a, |worktree, _| {
1357 assert!(worktree.as_local().unwrap().is_shared());
1358 worktree.id()
1359 });
1360
1361 // Drop client A's connection.
1362 server.forbid_connections();
1363 server.disconnect_client(client_a.peer_id().unwrap());
1364 deterministic.advance_clock(RECEIVE_TIMEOUT);
1365 project_a.read_with(cx_a, |project, _| {
1366 assert!(project.is_shared());
1367 assert_eq!(project.collaborators().len(), 1);
1368 });
1369 project_b.read_with(cx_b, |project, _| {
1370 assert!(!project.is_read_only());
1371 assert_eq!(project.collaborators().len(), 1);
1372 });
1373 worktree_a.read_with(cx_a, |tree, _| {
1374 assert!(tree.as_local().unwrap().is_shared())
1375 });
1376
1377 // While disconnected, add and remove files from the client A's project.
1378 client_a
1379 .fs
1380 .insert_tree(
1381 "/root/dir1/subdir2",
1382 json!({
1383 "f.txt": "f-contents",
1384 "g.txt": "g-contents",
1385 "h.txt": "h-contents",
1386 "i.txt": "i-contents",
1387 }),
1388 )
1389 .await;
1390 client_a
1391 .fs
1392 .remove_dir(
1393 "/root/dir1/subdir1".as_ref(),
1394 RemoveOptions {
1395 recursive: true,
1396 ..Default::default()
1397 },
1398 )
1399 .await
1400 .unwrap();
1401
1402 // Client A reconnects. Their project is re-shared, and client B re-joins it.
1403 server.allow_connections();
1404 client_a
1405 .authenticate_and_connect(false, &cx_a.to_async())
1406 .await
1407 .unwrap();
1408 deterministic.run_until_parked();
1409 project_a.read_with(cx_a, |project, cx| {
1410 assert!(project.is_shared());
1411 assert_eq!(
1412 worktree_a
1413 .read(cx)
1414 .snapshot()
1415 .paths()
1416 .map(|p| p.to_str().unwrap())
1417 .collect::<Vec<_>>(),
1418 vec![
1419 "a.txt",
1420 "b.txt",
1421 "subdir1",
1422 "subdir1/c.txt",
1423 "subdir1/d.txt",
1424 "subdir1/e.txt",
1425 "subdir2",
1426 "subdir2/f.txt",
1427 "subdir2/g.txt",
1428 "subdir2/h.txt",
1429 "subdir2/i.txt"
1430 ]
1431 );
1432 });
1433 project_b.read_with(cx_b, |project, cx| {
1434 assert!(!project.is_read_only());
1435 let worktree_b = project.worktree_for_id(worktree_id, cx).unwrap();
1436 assert_eq!(
1437 worktree_b
1438 .read(cx)
1439 .snapshot()
1440 .paths()
1441 .map(|p| p.to_str().unwrap())
1442 .collect::<Vec<_>>(),
1443 vec![
1444 "a.txt",
1445 "b.txt",
1446 "subdir1",
1447 "subdir1/c.txt",
1448 "subdir1/d.txt",
1449 "subdir1/e.txt",
1450 "subdir2",
1451 "subdir2/f.txt",
1452 "subdir2/g.txt",
1453 "subdir2/h.txt",
1454 "subdir2/i.txt"
1455 ]
1456 );
1457 });
1458}
1459
1460#[gpui::test(iterations = 10)]
1461async fn test_active_call_events(
1462 deterministic: Arc<Deterministic>,
1463 cx_a: &mut TestAppContext,
1464 cx_b: &mut TestAppContext,
1465) {
1466 deterministic.forbid_parking();
1467 let mut server = TestServer::start(&deterministic).await;
1468 let client_a = server.create_client(cx_a, "user_a").await;
1469 let client_b = server.create_client(cx_b, "user_b").await;
1470 client_a.fs.insert_tree("/a", json!({})).await;
1471 client_b.fs.insert_tree("/b", json!({})).await;
1472
1473 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1474 let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
1475
1476 server
1477 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1478 .await;
1479 let active_call_a = cx_a.read(ActiveCall::global);
1480 let active_call_b = cx_b.read(ActiveCall::global);
1481
1482 let events_a = active_call_events(cx_a);
1483 let events_b = active_call_events(cx_b);
1484
1485 let project_a_id = active_call_a
1486 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1487 .await
1488 .unwrap();
1489 deterministic.run_until_parked();
1490 assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1491 assert_eq!(
1492 mem::take(&mut *events_b.borrow_mut()),
1493 vec![room::Event::RemoteProjectShared {
1494 owner: Arc::new(User {
1495 id: client_a.user_id().unwrap(),
1496 github_login: "user_a".to_string(),
1497 avatar: None,
1498 }),
1499 project_id: project_a_id,
1500 worktree_root_names: vec!["a".to_string()],
1501 }]
1502 );
1503
1504 let project_b_id = active_call_b
1505 .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1506 .await
1507 .unwrap();
1508 deterministic.run_until_parked();
1509 assert_eq!(
1510 mem::take(&mut *events_a.borrow_mut()),
1511 vec![room::Event::RemoteProjectShared {
1512 owner: Arc::new(User {
1513 id: client_b.user_id().unwrap(),
1514 github_login: "user_b".to_string(),
1515 avatar: None,
1516 }),
1517 project_id: project_b_id,
1518 worktree_root_names: vec!["b".to_string()]
1519 }]
1520 );
1521 assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1522
1523 // Sharing a project twice is idempotent.
1524 let project_b_id_2 = active_call_b
1525 .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1526 .await
1527 .unwrap();
1528 assert_eq!(project_b_id_2, project_b_id);
1529 deterministic.run_until_parked();
1530 assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1531 assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1532}
1533
1534fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
1535 let events = Rc::new(RefCell::new(Vec::new()));
1536 let active_call = cx.read(ActiveCall::global);
1537 cx.update({
1538 let events = events.clone();
1539 |cx| {
1540 cx.subscribe(&active_call, move |_, event, _| {
1541 events.borrow_mut().push(event.clone())
1542 })
1543 .detach()
1544 }
1545 });
1546 events
1547}
1548
1549#[gpui::test(iterations = 10)]
1550async fn test_room_location(
1551 deterministic: Arc<Deterministic>,
1552 cx_a: &mut TestAppContext,
1553 cx_b: &mut TestAppContext,
1554) {
1555 deterministic.forbid_parking();
1556 let mut server = TestServer::start(&deterministic).await;
1557 let client_a = server.create_client(cx_a, "user_a").await;
1558 let client_b = server.create_client(cx_b, "user_b").await;
1559 client_a.fs.insert_tree("/a", json!({})).await;
1560 client_b.fs.insert_tree("/b", json!({})).await;
1561
1562 let active_call_a = cx_a.read(ActiveCall::global);
1563 let active_call_b = cx_b.read(ActiveCall::global);
1564
1565 let a_notified = Rc::new(Cell::new(false));
1566 cx_a.update({
1567 let notified = a_notified.clone();
1568 |cx| {
1569 cx.observe(&active_call_a, move |_, _| notified.set(true))
1570 .detach()
1571 }
1572 });
1573
1574 let b_notified = Rc::new(Cell::new(false));
1575 cx_b.update({
1576 let b_notified = b_notified.clone();
1577 |cx| {
1578 cx.observe(&active_call_b, move |_, _| b_notified.set(true))
1579 .detach()
1580 }
1581 });
1582
1583 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1584 active_call_a
1585 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1586 .await
1587 .unwrap();
1588 let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
1589
1590 server
1591 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1592 .await;
1593 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
1594 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
1595 deterministic.run_until_parked();
1596 assert!(a_notified.take());
1597 assert_eq!(
1598 participant_locations(&room_a, cx_a),
1599 vec![("user_b".to_string(), ParticipantLocation::External)]
1600 );
1601 assert!(b_notified.take());
1602 assert_eq!(
1603 participant_locations(&room_b, cx_b),
1604 vec![("user_a".to_string(), ParticipantLocation::UnsharedProject)]
1605 );
1606
1607 let project_a_id = active_call_a
1608 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1609 .await
1610 .unwrap();
1611 deterministic.run_until_parked();
1612 assert!(a_notified.take());
1613 assert_eq!(
1614 participant_locations(&room_a, cx_a),
1615 vec![("user_b".to_string(), ParticipantLocation::External)]
1616 );
1617 assert!(b_notified.take());
1618 assert_eq!(
1619 participant_locations(&room_b, cx_b),
1620 vec![(
1621 "user_a".to_string(),
1622 ParticipantLocation::SharedProject {
1623 project_id: project_a_id
1624 }
1625 )]
1626 );
1627
1628 let project_b_id = active_call_b
1629 .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1630 .await
1631 .unwrap();
1632 deterministic.run_until_parked();
1633 assert!(a_notified.take());
1634 assert_eq!(
1635 participant_locations(&room_a, cx_a),
1636 vec![("user_b".to_string(), ParticipantLocation::External)]
1637 );
1638 assert!(b_notified.take());
1639 assert_eq!(
1640 participant_locations(&room_b, cx_b),
1641 vec![(
1642 "user_a".to_string(),
1643 ParticipantLocation::SharedProject {
1644 project_id: project_a_id
1645 }
1646 )]
1647 );
1648
1649 active_call_b
1650 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1651 .await
1652 .unwrap();
1653 deterministic.run_until_parked();
1654 assert!(a_notified.take());
1655 assert_eq!(
1656 participant_locations(&room_a, cx_a),
1657 vec![(
1658 "user_b".to_string(),
1659 ParticipantLocation::SharedProject {
1660 project_id: project_b_id
1661 }
1662 )]
1663 );
1664 assert!(b_notified.take());
1665 assert_eq!(
1666 participant_locations(&room_b, cx_b),
1667 vec![(
1668 "user_a".to_string(),
1669 ParticipantLocation::SharedProject {
1670 project_id: project_a_id
1671 }
1672 )]
1673 );
1674
1675 active_call_b
1676 .update(cx_b, |call, cx| call.set_location(None, cx))
1677 .await
1678 .unwrap();
1679 deterministic.run_until_parked();
1680 assert!(a_notified.take());
1681 assert_eq!(
1682 participant_locations(&room_a, cx_a),
1683 vec![("user_b".to_string(), ParticipantLocation::External)]
1684 );
1685 assert!(b_notified.take());
1686 assert_eq!(
1687 participant_locations(&room_b, cx_b),
1688 vec![(
1689 "user_a".to_string(),
1690 ParticipantLocation::SharedProject {
1691 project_id: project_a_id
1692 }
1693 )]
1694 );
1695
1696 fn participant_locations(
1697 room: &ModelHandle<Room>,
1698 cx: &TestAppContext,
1699 ) -> Vec<(String, ParticipantLocation)> {
1700 room.read_with(cx, |room, _| {
1701 room.remote_participants()
1702 .values()
1703 .map(|participant| {
1704 (
1705 participant.user.github_login.to_string(),
1706 participant.location,
1707 )
1708 })
1709 .collect()
1710 })
1711 }
1712}
1713
1714#[gpui::test(iterations = 10)]
1715async fn test_propagate_saves_and_fs_changes(
1716 deterministic: Arc<Deterministic>,
1717 cx_a: &mut TestAppContext,
1718 cx_b: &mut TestAppContext,
1719 cx_c: &mut TestAppContext,
1720) {
1721 deterministic.forbid_parking();
1722 let mut server = TestServer::start(&deterministic).await;
1723 let client_a = server.create_client(cx_a, "user_a").await;
1724 let client_b = server.create_client(cx_b, "user_b").await;
1725 let client_c = server.create_client(cx_c, "user_c").await;
1726
1727 server
1728 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1729 .await;
1730 let active_call_a = cx_a.read(ActiveCall::global);
1731
1732 let rust = Arc::new(Language::new(
1733 LanguageConfig {
1734 name: "Rust".into(),
1735 path_suffixes: vec!["rs".to_string()],
1736 ..Default::default()
1737 },
1738 Some(tree_sitter_rust::language()),
1739 ));
1740 let javascript = Arc::new(Language::new(
1741 LanguageConfig {
1742 name: "JavaScript".into(),
1743 path_suffixes: vec!["js".to_string()],
1744 ..Default::default()
1745 },
1746 Some(tree_sitter_rust::language()),
1747 ));
1748 for client in [&client_a, &client_b, &client_c] {
1749 client.language_registry.add(rust.clone());
1750 client.language_registry.add(javascript.clone());
1751 }
1752
1753 client_a
1754 .fs
1755 .insert_tree(
1756 "/a",
1757 json!({
1758 "file1.rs": "",
1759 "file2": ""
1760 }),
1761 )
1762 .await;
1763 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1764 let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
1765 let project_id = active_call_a
1766 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1767 .await
1768 .unwrap();
1769
1770 // Join that worktree as clients B and C.
1771 let project_b = client_b.build_remote_project(project_id, cx_b).await;
1772 let project_c = client_c.build_remote_project(project_id, cx_c).await;
1773 let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
1774 let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
1775
1776 // Open and edit a buffer as both guests B and C.
1777 let buffer_b = project_b
1778 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
1779 .await
1780 .unwrap();
1781 let buffer_c = project_c
1782 .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
1783 .await
1784 .unwrap();
1785 buffer_b.read_with(cx_b, |buffer, _| {
1786 assert_eq!(&*buffer.language().unwrap().name(), "Rust");
1787 });
1788 buffer_c.read_with(cx_c, |buffer, _| {
1789 assert_eq!(&*buffer.language().unwrap().name(), "Rust");
1790 });
1791 buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
1792 buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
1793
1794 // Open and edit that buffer as the host.
1795 let buffer_a = project_a
1796 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
1797 .await
1798 .unwrap();
1799
1800 deterministic.run_until_parked();
1801 buffer_a.read_with(cx_a, |buf, _| assert_eq!(buf.text(), "i-am-c, i-am-b, "));
1802 buffer_a.update(cx_a, |buf, cx| {
1803 buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
1804 });
1805
1806 deterministic.run_until_parked();
1807 buffer_a.read_with(cx_a, |buf, _| {
1808 assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
1809 });
1810 buffer_b.read_with(cx_b, |buf, _| {
1811 assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
1812 });
1813 buffer_c.read_with(cx_c, |buf, _| {
1814 assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
1815 });
1816
1817 // Edit the buffer as the host and concurrently save as guest B.
1818 let save_b = buffer_b.update(cx_b, |buf, cx| buf.save(cx));
1819 buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
1820 save_b.await.unwrap();
1821 assert_eq!(
1822 client_a.fs.load("/a/file1.rs".as_ref()).await.unwrap(),
1823 "hi-a, i-am-c, i-am-b, i-am-a"
1824 );
1825
1826 deterministic.run_until_parked();
1827 buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
1828 buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
1829 buffer_c.read_with(cx_c, |buf, _| assert!(!buf.is_dirty()));
1830
1831 // Make changes on host's file system, see those changes on guest worktrees.
1832 client_a
1833 .fs
1834 .rename(
1835 "/a/file1.rs".as_ref(),
1836 "/a/file1.js".as_ref(),
1837 Default::default(),
1838 )
1839 .await
1840 .unwrap();
1841 client_a
1842 .fs
1843 .rename("/a/file2".as_ref(), "/a/file3".as_ref(), Default::default())
1844 .await
1845 .unwrap();
1846 client_a.fs.insert_file("/a/file4", "4".into()).await;
1847 deterministic.run_until_parked();
1848
1849 worktree_a.read_with(cx_a, |tree, _| {
1850 assert_eq!(
1851 tree.paths()
1852 .map(|p| p.to_string_lossy())
1853 .collect::<Vec<_>>(),
1854 ["file1.js", "file3", "file4"]
1855 )
1856 });
1857 worktree_b.read_with(cx_b, |tree, _| {
1858 assert_eq!(
1859 tree.paths()
1860 .map(|p| p.to_string_lossy())
1861 .collect::<Vec<_>>(),
1862 ["file1.js", "file3", "file4"]
1863 )
1864 });
1865 worktree_c.read_with(cx_c, |tree, _| {
1866 assert_eq!(
1867 tree.paths()
1868 .map(|p| p.to_string_lossy())
1869 .collect::<Vec<_>>(),
1870 ["file1.js", "file3", "file4"]
1871 )
1872 });
1873
1874 // Ensure buffer files are updated as well.
1875 buffer_a.read_with(cx_a, |buffer, _| {
1876 assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
1877 assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
1878 });
1879 buffer_b.read_with(cx_b, |buffer, _| {
1880 assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
1881 assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
1882 });
1883 buffer_c.read_with(cx_c, |buffer, _| {
1884 assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
1885 assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
1886 });
1887}
1888
1889#[gpui::test(iterations = 10)]
1890async fn test_git_diff_base_change(
1891 deterministic: Arc<Deterministic>,
1892 cx_a: &mut TestAppContext,
1893 cx_b: &mut TestAppContext,
1894) {
1895 deterministic.forbid_parking();
1896 let mut server = TestServer::start(&deterministic).await;
1897 let client_a = server.create_client(cx_a, "user_a").await;
1898 let client_b = server.create_client(cx_b, "user_b").await;
1899 server
1900 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1901 .await;
1902 let active_call_a = cx_a.read(ActiveCall::global);
1903
1904 client_a
1905 .fs
1906 .insert_tree(
1907 "/dir",
1908 json!({
1909 ".git": {},
1910 "sub": {
1911 ".git": {},
1912 "b.txt": "
1913 one
1914 two
1915 three
1916 ".unindent(),
1917 },
1918 "a.txt": "
1919 one
1920 two
1921 three
1922 ".unindent(),
1923 }),
1924 )
1925 .await;
1926
1927 let (project_local, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1928 let project_id = active_call_a
1929 .update(cx_a, |call, cx| {
1930 call.share_project(project_local.clone(), cx)
1931 })
1932 .await
1933 .unwrap();
1934
1935 let project_remote = client_b.build_remote_project(project_id, cx_b).await;
1936
1937 let diff_base = "
1938 one
1939 three
1940 "
1941 .unindent();
1942
1943 let new_diff_base = "
1944 one
1945 two
1946 "
1947 .unindent();
1948
1949 client_a
1950 .fs
1951 .as_fake()
1952 .set_index_for_repo(
1953 Path::new("/dir/.git"),
1954 &[(Path::new("a.txt"), diff_base.clone())],
1955 )
1956 .await;
1957
1958 // Create the buffer
1959 let buffer_local_a = project_local
1960 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1961 .await
1962 .unwrap();
1963
1964 // Wait for it to catch up to the new diff
1965 deterministic.run_until_parked();
1966
1967 // Smoke test diffing
1968 buffer_local_a.read_with(cx_a, |buffer, _| {
1969 assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
1970 git::diff::assert_hunks(
1971 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
1972 &buffer,
1973 &diff_base,
1974 &[(1..2, "", "two\n")],
1975 );
1976 });
1977
1978 // Create remote buffer
1979 let buffer_remote_a = project_remote
1980 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1981 .await
1982 .unwrap();
1983
1984 // Wait remote buffer to catch up to the new diff
1985 deterministic.run_until_parked();
1986
1987 // Smoke test diffing
1988 buffer_remote_a.read_with(cx_b, |buffer, _| {
1989 assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
1990 git::diff::assert_hunks(
1991 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
1992 &buffer,
1993 &diff_base,
1994 &[(1..2, "", "two\n")],
1995 );
1996 });
1997
1998 client_a
1999 .fs
2000 .as_fake()
2001 .set_index_for_repo(
2002 Path::new("/dir/.git"),
2003 &[(Path::new("a.txt"), new_diff_base.clone())],
2004 )
2005 .await;
2006
2007 // Wait for buffer_local_a to receive it
2008 deterministic.run_until_parked();
2009
2010 // Smoke test new diffing
2011 buffer_local_a.read_with(cx_a, |buffer, _| {
2012 assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
2013
2014 git::diff::assert_hunks(
2015 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2016 &buffer,
2017 &diff_base,
2018 &[(2..3, "", "three\n")],
2019 );
2020 });
2021
2022 // Smoke test B
2023 buffer_remote_a.read_with(cx_b, |buffer, _| {
2024 assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
2025 git::diff::assert_hunks(
2026 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2027 &buffer,
2028 &diff_base,
2029 &[(2..3, "", "three\n")],
2030 );
2031 });
2032
2033 //Nested git dir
2034
2035 let diff_base = "
2036 one
2037 three
2038 "
2039 .unindent();
2040
2041 let new_diff_base = "
2042 one
2043 two
2044 "
2045 .unindent();
2046
2047 client_a
2048 .fs
2049 .as_fake()
2050 .set_index_for_repo(
2051 Path::new("/dir/sub/.git"),
2052 &[(Path::new("b.txt"), diff_base.clone())],
2053 )
2054 .await;
2055
2056 // Create the buffer
2057 let buffer_local_b = project_local
2058 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2059 .await
2060 .unwrap();
2061
2062 // Wait for it to catch up to the new diff
2063 deterministic.run_until_parked();
2064
2065 // Smoke test diffing
2066 buffer_local_b.read_with(cx_a, |buffer, _| {
2067 assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
2068 git::diff::assert_hunks(
2069 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2070 &buffer,
2071 &diff_base,
2072 &[(1..2, "", "two\n")],
2073 );
2074 });
2075
2076 // Create remote buffer
2077 let buffer_remote_b = project_remote
2078 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2079 .await
2080 .unwrap();
2081
2082 // Wait remote buffer to catch up to the new diff
2083 deterministic.run_until_parked();
2084
2085 // Smoke test diffing
2086 buffer_remote_b.read_with(cx_b, |buffer, _| {
2087 assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
2088 git::diff::assert_hunks(
2089 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2090 &buffer,
2091 &diff_base,
2092 &[(1..2, "", "two\n")],
2093 );
2094 });
2095
2096 client_a
2097 .fs
2098 .as_fake()
2099 .set_index_for_repo(
2100 Path::new("/dir/sub/.git"),
2101 &[(Path::new("b.txt"), new_diff_base.clone())],
2102 )
2103 .await;
2104
2105 // Wait for buffer_local_b to receive it
2106 deterministic.run_until_parked();
2107
2108 // Smoke test new diffing
2109 buffer_local_b.read_with(cx_a, |buffer, _| {
2110 assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
2111 println!("{:?}", buffer.as_rope().to_string());
2112 println!("{:?}", buffer.diff_base());
2113 println!(
2114 "{:?}",
2115 buffer
2116 .snapshot()
2117 .git_diff_hunks_in_range(0..4, false)
2118 .collect::<Vec<_>>()
2119 );
2120
2121 git::diff::assert_hunks(
2122 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2123 &buffer,
2124 &diff_base,
2125 &[(2..3, "", "three\n")],
2126 );
2127 });
2128
2129 // Smoke test B
2130 buffer_remote_b.read_with(cx_b, |buffer, _| {
2131 assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
2132 git::diff::assert_hunks(
2133 buffer.snapshot().git_diff_hunks_in_range(0..4, false),
2134 &buffer,
2135 &diff_base,
2136 &[(2..3, "", "three\n")],
2137 );
2138 });
2139}
2140
2141#[gpui::test(iterations = 10)]
2142async fn test_fs_operations(
2143 deterministic: Arc<Deterministic>,
2144 cx_a: &mut TestAppContext,
2145 cx_b: &mut TestAppContext,
2146) {
2147 deterministic.forbid_parking();
2148 let mut server = TestServer::start(&deterministic).await;
2149 let client_a = server.create_client(cx_a, "user_a").await;
2150 let client_b = server.create_client(cx_b, "user_b").await;
2151 server
2152 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2153 .await;
2154 let active_call_a = cx_a.read(ActiveCall::global);
2155
2156 client_a
2157 .fs
2158 .insert_tree(
2159 "/dir",
2160 json!({
2161 "a.txt": "a-contents",
2162 "b.txt": "b-contents",
2163 }),
2164 )
2165 .await;
2166 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2167 let project_id = active_call_a
2168 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2169 .await
2170 .unwrap();
2171 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2172
2173 let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
2174 let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
2175
2176 let entry = project_b
2177 .update(cx_b, |project, cx| {
2178 project
2179 .create_entry((worktree_id, "c.txt"), false, cx)
2180 .unwrap()
2181 })
2182 .await
2183 .unwrap();
2184 worktree_a.read_with(cx_a, |worktree, _| {
2185 assert_eq!(
2186 worktree
2187 .paths()
2188 .map(|p| p.to_string_lossy())
2189 .collect::<Vec<_>>(),
2190 ["a.txt", "b.txt", "c.txt"]
2191 );
2192 });
2193 worktree_b.read_with(cx_b, |worktree, _| {
2194 assert_eq!(
2195 worktree
2196 .paths()
2197 .map(|p| p.to_string_lossy())
2198 .collect::<Vec<_>>(),
2199 ["a.txt", "b.txt", "c.txt"]
2200 );
2201 });
2202
2203 project_b
2204 .update(cx_b, |project, cx| {
2205 project.rename_entry(entry.id, Path::new("d.txt"), cx)
2206 })
2207 .unwrap()
2208 .await
2209 .unwrap();
2210 worktree_a.read_with(cx_a, |worktree, _| {
2211 assert_eq!(
2212 worktree
2213 .paths()
2214 .map(|p| p.to_string_lossy())
2215 .collect::<Vec<_>>(),
2216 ["a.txt", "b.txt", "d.txt"]
2217 );
2218 });
2219 worktree_b.read_with(cx_b, |worktree, _| {
2220 assert_eq!(
2221 worktree
2222 .paths()
2223 .map(|p| p.to_string_lossy())
2224 .collect::<Vec<_>>(),
2225 ["a.txt", "b.txt", "d.txt"]
2226 );
2227 });
2228
2229 let dir_entry = project_b
2230 .update(cx_b, |project, cx| {
2231 project
2232 .create_entry((worktree_id, "DIR"), true, cx)
2233 .unwrap()
2234 })
2235 .await
2236 .unwrap();
2237 worktree_a.read_with(cx_a, |worktree, _| {
2238 assert_eq!(
2239 worktree
2240 .paths()
2241 .map(|p| p.to_string_lossy())
2242 .collect::<Vec<_>>(),
2243 ["DIR", "a.txt", "b.txt", "d.txt"]
2244 );
2245 });
2246 worktree_b.read_with(cx_b, |worktree, _| {
2247 assert_eq!(
2248 worktree
2249 .paths()
2250 .map(|p| p.to_string_lossy())
2251 .collect::<Vec<_>>(),
2252 ["DIR", "a.txt", "b.txt", "d.txt"]
2253 );
2254 });
2255
2256 project_b
2257 .update(cx_b, |project, cx| {
2258 project
2259 .create_entry((worktree_id, "DIR/e.txt"), false, cx)
2260 .unwrap()
2261 })
2262 .await
2263 .unwrap();
2264 project_b
2265 .update(cx_b, |project, cx| {
2266 project
2267 .create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
2268 .unwrap()
2269 })
2270 .await
2271 .unwrap();
2272 project_b
2273 .update(cx_b, |project, cx| {
2274 project
2275 .create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
2276 .unwrap()
2277 })
2278 .await
2279 .unwrap();
2280 worktree_a.read_with(cx_a, |worktree, _| {
2281 assert_eq!(
2282 worktree
2283 .paths()
2284 .map(|p| p.to_string_lossy())
2285 .collect::<Vec<_>>(),
2286 [
2287 "DIR",
2288 "DIR/SUBDIR",
2289 "DIR/SUBDIR/f.txt",
2290 "DIR/e.txt",
2291 "a.txt",
2292 "b.txt",
2293 "d.txt"
2294 ]
2295 );
2296 });
2297 worktree_b.read_with(cx_b, |worktree, _| {
2298 assert_eq!(
2299 worktree
2300 .paths()
2301 .map(|p| p.to_string_lossy())
2302 .collect::<Vec<_>>(),
2303 [
2304 "DIR",
2305 "DIR/SUBDIR",
2306 "DIR/SUBDIR/f.txt",
2307 "DIR/e.txt",
2308 "a.txt",
2309 "b.txt",
2310 "d.txt"
2311 ]
2312 );
2313 });
2314
2315 project_b
2316 .update(cx_b, |project, cx| {
2317 project
2318 .copy_entry(entry.id, Path::new("f.txt"), cx)
2319 .unwrap()
2320 })
2321 .await
2322 .unwrap();
2323 worktree_a.read_with(cx_a, |worktree, _| {
2324 assert_eq!(
2325 worktree
2326 .paths()
2327 .map(|p| p.to_string_lossy())
2328 .collect::<Vec<_>>(),
2329 [
2330 "DIR",
2331 "DIR/SUBDIR",
2332 "DIR/SUBDIR/f.txt",
2333 "DIR/e.txt",
2334 "a.txt",
2335 "b.txt",
2336 "d.txt",
2337 "f.txt"
2338 ]
2339 );
2340 });
2341 worktree_b.read_with(cx_b, |worktree, _| {
2342 assert_eq!(
2343 worktree
2344 .paths()
2345 .map(|p| p.to_string_lossy())
2346 .collect::<Vec<_>>(),
2347 [
2348 "DIR",
2349 "DIR/SUBDIR",
2350 "DIR/SUBDIR/f.txt",
2351 "DIR/e.txt",
2352 "a.txt",
2353 "b.txt",
2354 "d.txt",
2355 "f.txt"
2356 ]
2357 );
2358 });
2359
2360 project_b
2361 .update(cx_b, |project, cx| {
2362 project.delete_entry(dir_entry.id, cx).unwrap()
2363 })
2364 .await
2365 .unwrap();
2366 worktree_a.read_with(cx_a, |worktree, _| {
2367 assert_eq!(
2368 worktree
2369 .paths()
2370 .map(|p| p.to_string_lossy())
2371 .collect::<Vec<_>>(),
2372 ["a.txt", "b.txt", "d.txt", "f.txt"]
2373 );
2374 });
2375 worktree_b.read_with(cx_b, |worktree, _| {
2376 assert_eq!(
2377 worktree
2378 .paths()
2379 .map(|p| p.to_string_lossy())
2380 .collect::<Vec<_>>(),
2381 ["a.txt", "b.txt", "d.txt", "f.txt"]
2382 );
2383 });
2384
2385 project_b
2386 .update(cx_b, |project, cx| {
2387 project.delete_entry(entry.id, cx).unwrap()
2388 })
2389 .await
2390 .unwrap();
2391 worktree_a.read_with(cx_a, |worktree, _| {
2392 assert_eq!(
2393 worktree
2394 .paths()
2395 .map(|p| p.to_string_lossy())
2396 .collect::<Vec<_>>(),
2397 ["a.txt", "b.txt", "f.txt"]
2398 );
2399 });
2400 worktree_b.read_with(cx_b, |worktree, _| {
2401 assert_eq!(
2402 worktree
2403 .paths()
2404 .map(|p| p.to_string_lossy())
2405 .collect::<Vec<_>>(),
2406 ["a.txt", "b.txt", "f.txt"]
2407 );
2408 });
2409}
2410
2411#[gpui::test(iterations = 10)]
2412async fn test_buffer_conflict_after_save(
2413 deterministic: Arc<Deterministic>,
2414 cx_a: &mut TestAppContext,
2415 cx_b: &mut TestAppContext,
2416) {
2417 deterministic.forbid_parking();
2418 let mut server = TestServer::start(&deterministic).await;
2419 let client_a = server.create_client(cx_a, "user_a").await;
2420 let client_b = server.create_client(cx_b, "user_b").await;
2421 server
2422 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2423 .await;
2424 let active_call_a = cx_a.read(ActiveCall::global);
2425
2426 client_a
2427 .fs
2428 .insert_tree(
2429 "/dir",
2430 json!({
2431 "a.txt": "a-contents",
2432 }),
2433 )
2434 .await;
2435 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2436 let project_id = active_call_a
2437 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2438 .await
2439 .unwrap();
2440 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2441
2442 // Open a buffer as client B
2443 let buffer_b = project_b
2444 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2445 .await
2446 .unwrap();
2447
2448 buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
2449 buffer_b.read_with(cx_b, |buf, _| {
2450 assert!(buf.is_dirty());
2451 assert!(!buf.has_conflict());
2452 });
2453
2454 buffer_b.update(cx_b, |buf, cx| buf.save(cx)).await.unwrap();
2455 cx_a.foreground().forbid_parking();
2456 buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
2457 buffer_b.read_with(cx_b, |buf, _| {
2458 assert!(!buf.has_conflict());
2459 });
2460
2461 buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
2462 buffer_b.read_with(cx_b, |buf, _| {
2463 assert!(buf.is_dirty());
2464 assert!(!buf.has_conflict());
2465 });
2466}
2467
2468#[gpui::test(iterations = 10)]
2469async fn test_buffer_reloading(
2470 deterministic: Arc<Deterministic>,
2471 cx_a: &mut TestAppContext,
2472 cx_b: &mut TestAppContext,
2473) {
2474 deterministic.forbid_parking();
2475 let mut server = TestServer::start(&deterministic).await;
2476 let client_a = server.create_client(cx_a, "user_a").await;
2477 let client_b = server.create_client(cx_b, "user_b").await;
2478 server
2479 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2480 .await;
2481 let active_call_a = cx_a.read(ActiveCall::global);
2482
2483 client_a
2484 .fs
2485 .insert_tree(
2486 "/dir",
2487 json!({
2488 "a.txt": "a\nb\nc",
2489 }),
2490 )
2491 .await;
2492 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2493 let project_id = active_call_a
2494 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2495 .await
2496 .unwrap();
2497 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2498
2499 // Open a buffer as client B
2500 let buffer_b = project_b
2501 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2502 .await
2503 .unwrap();
2504 buffer_b.read_with(cx_b, |buf, _| {
2505 assert!(!buf.is_dirty());
2506 assert!(!buf.has_conflict());
2507 assert_eq!(buf.line_ending(), LineEnding::Unix);
2508 });
2509
2510 let new_contents = Rope::from("d\ne\nf");
2511 client_a
2512 .fs
2513 .save("/dir/a.txt".as_ref(), &new_contents, LineEnding::Windows)
2514 .await
2515 .unwrap();
2516 cx_a.foreground().run_until_parked();
2517 buffer_b.read_with(cx_b, |buf, _| {
2518 assert_eq!(buf.text(), new_contents.to_string());
2519 assert!(!buf.is_dirty());
2520 assert!(!buf.has_conflict());
2521 assert_eq!(buf.line_ending(), LineEnding::Windows);
2522 });
2523}
2524
2525#[gpui::test(iterations = 10)]
2526async fn test_editing_while_guest_opens_buffer(
2527 deterministic: Arc<Deterministic>,
2528 cx_a: &mut TestAppContext,
2529 cx_b: &mut TestAppContext,
2530) {
2531 deterministic.forbid_parking();
2532 let mut server = TestServer::start(&deterministic).await;
2533 let client_a = server.create_client(cx_a, "user_a").await;
2534 let client_b = server.create_client(cx_b, "user_b").await;
2535 server
2536 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2537 .await;
2538 let active_call_a = cx_a.read(ActiveCall::global);
2539
2540 client_a
2541 .fs
2542 .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
2543 .await;
2544 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2545 let project_id = active_call_a
2546 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2547 .await
2548 .unwrap();
2549 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2550
2551 // Open a buffer as client A
2552 let buffer_a = project_a
2553 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2554 .await
2555 .unwrap();
2556
2557 // Start opening the same buffer as client B
2558 let buffer_b = cx_b
2559 .background()
2560 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
2561
2562 // Edit the buffer as client A while client B is still opening it.
2563 cx_b.background().simulate_random_delay().await;
2564 buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
2565 cx_b.background().simulate_random_delay().await;
2566 buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
2567
2568 let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
2569 let buffer_b = buffer_b.await.unwrap();
2570 cx_a.foreground().run_until_parked();
2571 buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
2572}
2573
2574#[gpui::test(iterations = 10)]
2575async fn test_leaving_worktree_while_opening_buffer(
2576 deterministic: Arc<Deterministic>,
2577 cx_a: &mut TestAppContext,
2578 cx_b: &mut TestAppContext,
2579) {
2580 deterministic.forbid_parking();
2581 let mut server = TestServer::start(&deterministic).await;
2582 let client_a = server.create_client(cx_a, "user_a").await;
2583 let client_b = server.create_client(cx_b, "user_b").await;
2584 server
2585 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2586 .await;
2587 let active_call_a = cx_a.read(ActiveCall::global);
2588
2589 client_a
2590 .fs
2591 .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
2592 .await;
2593 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2594 let project_id = active_call_a
2595 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2596 .await
2597 .unwrap();
2598 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2599
2600 // See that a guest has joined as client A.
2601 cx_a.foreground().run_until_parked();
2602 project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
2603
2604 // Begin opening a buffer as client B, but leave the project before the open completes.
2605 let buffer_b = cx_b
2606 .background()
2607 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
2608 cx_b.update(|_| drop(project_b));
2609 drop(buffer_b);
2610
2611 // See that the guest has left.
2612 cx_a.foreground().run_until_parked();
2613 project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
2614}
2615
2616#[gpui::test(iterations = 10)]
2617async fn test_canceling_buffer_opening(
2618 deterministic: Arc<Deterministic>,
2619 cx_a: &mut TestAppContext,
2620 cx_b: &mut TestAppContext,
2621) {
2622 deterministic.forbid_parking();
2623
2624 let mut server = TestServer::start(&deterministic).await;
2625 let client_a = server.create_client(cx_a, "user_a").await;
2626 let client_b = server.create_client(cx_b, "user_b").await;
2627 server
2628 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2629 .await;
2630 let active_call_a = cx_a.read(ActiveCall::global);
2631
2632 client_a
2633 .fs
2634 .insert_tree(
2635 "/dir",
2636 json!({
2637 "a.txt": "abc",
2638 }),
2639 )
2640 .await;
2641 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2642 let project_id = active_call_a
2643 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2644 .await
2645 .unwrap();
2646 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2647
2648 let buffer_a = project_a
2649 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2650 .await
2651 .unwrap();
2652
2653 // Open a buffer as client B but cancel after a random amount of time.
2654 let buffer_b = project_b.update(cx_b, |p, cx| p.open_buffer_by_id(buffer_a.id() as u64, cx));
2655 deterministic.simulate_random_delay().await;
2656 drop(buffer_b);
2657
2658 // Try opening the same buffer again as client B, and ensure we can
2659 // still do it despite the cancellation above.
2660 let buffer_b = project_b
2661 .update(cx_b, |p, cx| p.open_buffer_by_id(buffer_a.id() as u64, cx))
2662 .await
2663 .unwrap();
2664 buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
2665}
2666
2667#[gpui::test(iterations = 10)]
2668async fn test_leaving_project(
2669 deterministic: Arc<Deterministic>,
2670 cx_a: &mut TestAppContext,
2671 cx_b: &mut TestAppContext,
2672 cx_c: &mut TestAppContext,
2673) {
2674 deterministic.forbid_parking();
2675 let mut server = TestServer::start(&deterministic).await;
2676 let client_a = server.create_client(cx_a, "user_a").await;
2677 let client_b = server.create_client(cx_b, "user_b").await;
2678 let client_c = server.create_client(cx_c, "user_c").await;
2679 server
2680 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2681 .await;
2682 let active_call_a = cx_a.read(ActiveCall::global);
2683
2684 client_a
2685 .fs
2686 .insert_tree(
2687 "/a",
2688 json!({
2689 "a.txt": "a-contents",
2690 "b.txt": "b-contents",
2691 }),
2692 )
2693 .await;
2694 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
2695 let project_id = active_call_a
2696 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2697 .await
2698 .unwrap();
2699 let project_b1 = client_b.build_remote_project(project_id, cx_b).await;
2700 let project_c = client_c.build_remote_project(project_id, cx_c).await;
2701
2702 // Client A sees that a guest has joined.
2703 deterministic.run_until_parked();
2704 project_a.read_with(cx_a, |project, _| {
2705 assert_eq!(project.collaborators().len(), 2);
2706 });
2707 project_b1.read_with(cx_b, |project, _| {
2708 assert_eq!(project.collaborators().len(), 2);
2709 });
2710 project_c.read_with(cx_c, |project, _| {
2711 assert_eq!(project.collaborators().len(), 2);
2712 });
2713
2714 // Client B opens a buffer.
2715 let buffer_b1 = project_b1
2716 .update(cx_b, |project, cx| {
2717 let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
2718 project.open_buffer((worktree_id, "a.txt"), cx)
2719 })
2720 .await
2721 .unwrap();
2722 buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
2723
2724 // Drop client B's project and ensure client A and client C observe client B leaving.
2725 cx_b.update(|_| drop(project_b1));
2726 deterministic.run_until_parked();
2727 project_a.read_with(cx_a, |project, _| {
2728 assert_eq!(project.collaborators().len(), 1);
2729 });
2730 project_c.read_with(cx_c, |project, _| {
2731 assert_eq!(project.collaborators().len(), 1);
2732 });
2733
2734 // Client B re-joins the project and can open buffers as before.
2735 let project_b2 = client_b.build_remote_project(project_id, cx_b).await;
2736 deterministic.run_until_parked();
2737 project_a.read_with(cx_a, |project, _| {
2738 assert_eq!(project.collaborators().len(), 2);
2739 });
2740 project_b2.read_with(cx_b, |project, _| {
2741 assert_eq!(project.collaborators().len(), 2);
2742 });
2743 project_c.read_with(cx_c, |project, _| {
2744 assert_eq!(project.collaborators().len(), 2);
2745 });
2746
2747 let buffer_b2 = project_b2
2748 .update(cx_b, |project, cx| {
2749 let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
2750 project.open_buffer((worktree_id, "a.txt"), cx)
2751 })
2752 .await
2753 .unwrap();
2754 buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
2755
2756 // Drop client B's connection and ensure client A and client C observe client B leaving.
2757 client_b.disconnect(&cx_b.to_async()).unwrap();
2758 deterministic.run_until_parked();
2759 project_a.read_with(cx_a, |project, _| {
2760 assert_eq!(project.collaborators().len(), 1);
2761 });
2762 project_b2.read_with(cx_b, |project, _| {
2763 assert!(project.is_read_only());
2764 });
2765 project_c.read_with(cx_c, |project, _| {
2766 assert_eq!(project.collaborators().len(), 1);
2767 });
2768
2769 // Client B can't join the project, unless they re-join the room.
2770 cx_b.spawn(|cx| {
2771 Project::remote(
2772 project_id,
2773 client_b.client.clone(),
2774 client_b.user_store.clone(),
2775 client_b.language_registry.clone(),
2776 FakeFs::new(cx.background()),
2777 cx,
2778 )
2779 })
2780 .await
2781 .unwrap_err();
2782
2783 // Simulate connection loss for client C and ensure client A observes client C leaving the project.
2784 client_c.wait_for_current_user(cx_c).await;
2785 server.disconnect_client(client_c.peer_id().unwrap());
2786 deterministic.advance_clock(RECEIVE_TIMEOUT);
2787 deterministic.run_until_parked();
2788 project_a.read_with(cx_a, |project, _| {
2789 assert_eq!(project.collaborators().len(), 0);
2790 });
2791 project_b2.read_with(cx_b, |project, _| {
2792 assert!(project.is_read_only());
2793 });
2794 project_c.read_with(cx_c, |project, _| {
2795 assert!(project.is_read_only());
2796 });
2797}
2798
2799#[gpui::test(iterations = 10)]
2800async fn test_collaborating_with_diagnostics(
2801 deterministic: Arc<Deterministic>,
2802 cx_a: &mut TestAppContext,
2803 cx_b: &mut TestAppContext,
2804 cx_c: &mut TestAppContext,
2805) {
2806 deterministic.forbid_parking();
2807 let mut server = TestServer::start(&deterministic).await;
2808 let client_a = server.create_client(cx_a, "user_a").await;
2809 let client_b = server.create_client(cx_b, "user_b").await;
2810 let client_c = server.create_client(cx_c, "user_c").await;
2811 server
2812 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2813 .await;
2814 let active_call_a = cx_a.read(ActiveCall::global);
2815
2816 // Set up a fake language server.
2817 let mut language = Language::new(
2818 LanguageConfig {
2819 name: "Rust".into(),
2820 path_suffixes: vec!["rs".to_string()],
2821 ..Default::default()
2822 },
2823 Some(tree_sitter_rust::language()),
2824 );
2825 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2826 client_a.language_registry.add(Arc::new(language));
2827
2828 // Share a project as client A
2829 client_a
2830 .fs
2831 .insert_tree(
2832 "/a",
2833 json!({
2834 "a.rs": "let one = two",
2835 "other.rs": "",
2836 }),
2837 )
2838 .await;
2839 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2840
2841 // Cause the language server to start.
2842 let _buffer = project_a
2843 .update(cx_a, |project, cx| {
2844 project.open_buffer(
2845 ProjectPath {
2846 worktree_id,
2847 path: Path::new("other.rs").into(),
2848 },
2849 cx,
2850 )
2851 })
2852 .await
2853 .unwrap();
2854
2855 // Simulate a language server reporting errors for a file.
2856 let mut fake_language_server = fake_language_servers.next().await.unwrap();
2857 fake_language_server
2858 .receive_notification::<lsp::notification::DidOpenTextDocument>()
2859 .await;
2860 fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2861 lsp::PublishDiagnosticsParams {
2862 uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2863 version: None,
2864 diagnostics: vec![lsp::Diagnostic {
2865 severity: Some(lsp::DiagnosticSeverity::WARNING),
2866 range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
2867 message: "message 0".to_string(),
2868 ..Default::default()
2869 }],
2870 },
2871 );
2872
2873 // Client A shares the project and, simultaneously, the language server
2874 // publishes a diagnostic. This is done to ensure that the server always
2875 // observes the latest diagnostics for a worktree.
2876 let project_id = active_call_a
2877 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2878 .await
2879 .unwrap();
2880 fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2881 lsp::PublishDiagnosticsParams {
2882 uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2883 version: None,
2884 diagnostics: vec![lsp::Diagnostic {
2885 severity: Some(lsp::DiagnosticSeverity::ERROR),
2886 range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
2887 message: "message 1".to_string(),
2888 ..Default::default()
2889 }],
2890 },
2891 );
2892
2893 // Join the worktree as client B.
2894 let project_b = client_b.build_remote_project(project_id, cx_b).await;
2895
2896 // Wait for server to see the diagnostics update.
2897 deterministic.run_until_parked();
2898
2899 // Ensure client B observes the new diagnostics.
2900 project_b.read_with(cx_b, |project, cx| {
2901 assert_eq!(
2902 project.diagnostic_summaries(cx).collect::<Vec<_>>(),
2903 &[(
2904 ProjectPath {
2905 worktree_id,
2906 path: Arc::from(Path::new("a.rs")),
2907 },
2908 DiagnosticSummary {
2909 error_count: 1,
2910 warning_count: 0,
2911 ..Default::default()
2912 },
2913 )]
2914 )
2915 });
2916
2917 // Join project as client C and observe the diagnostics.
2918 let project_c = client_c.build_remote_project(project_id, cx_c).await;
2919 let project_c_diagnostic_summaries =
2920 Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
2921 project.diagnostic_summaries(cx).collect::<Vec<_>>()
2922 })));
2923 project_c.update(cx_c, |_, cx| {
2924 let summaries = project_c_diagnostic_summaries.clone();
2925 cx.subscribe(&project_c, {
2926 move |p, _, event, cx| {
2927 if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
2928 *summaries.borrow_mut() = p.diagnostic_summaries(cx).collect();
2929 }
2930 }
2931 })
2932 .detach();
2933 });
2934
2935 deterministic.run_until_parked();
2936 assert_eq!(
2937 project_c_diagnostic_summaries.borrow().as_slice(),
2938 &[(
2939 ProjectPath {
2940 worktree_id,
2941 path: Arc::from(Path::new("a.rs")),
2942 },
2943 DiagnosticSummary {
2944 error_count: 1,
2945 warning_count: 0,
2946 ..Default::default()
2947 },
2948 )]
2949 );
2950
2951 // Simulate a language server reporting more errors for a file.
2952 fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2953 lsp::PublishDiagnosticsParams {
2954 uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2955 version: None,
2956 diagnostics: vec![
2957 lsp::Diagnostic {
2958 severity: Some(lsp::DiagnosticSeverity::ERROR),
2959 range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
2960 message: "message 1".to_string(),
2961 ..Default::default()
2962 },
2963 lsp::Diagnostic {
2964 severity: Some(lsp::DiagnosticSeverity::WARNING),
2965 range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
2966 message: "message 2".to_string(),
2967 ..Default::default()
2968 },
2969 ],
2970 },
2971 );
2972
2973 // Clients B and C get the updated summaries
2974 deterministic.run_until_parked();
2975 project_b.read_with(cx_b, |project, cx| {
2976 assert_eq!(
2977 project.diagnostic_summaries(cx).collect::<Vec<_>>(),
2978 [(
2979 ProjectPath {
2980 worktree_id,
2981 path: Arc::from(Path::new("a.rs")),
2982 },
2983 DiagnosticSummary {
2984 error_count: 1,
2985 warning_count: 1,
2986 ..Default::default()
2987 },
2988 )]
2989 );
2990 });
2991 project_c.read_with(cx_c, |project, cx| {
2992 assert_eq!(
2993 project.diagnostic_summaries(cx).collect::<Vec<_>>(),
2994 [(
2995 ProjectPath {
2996 worktree_id,
2997 path: Arc::from(Path::new("a.rs")),
2998 },
2999 DiagnosticSummary {
3000 error_count: 1,
3001 warning_count: 1,
3002 ..Default::default()
3003 },
3004 )]
3005 );
3006 });
3007
3008 // Open the file with the errors on client B. They should be present.
3009 let buffer_b = cx_b
3010 .background()
3011 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3012 .await
3013 .unwrap();
3014
3015 buffer_b.read_with(cx_b, |buffer, _| {
3016 assert_eq!(
3017 buffer
3018 .snapshot()
3019 .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
3020 .collect::<Vec<_>>(),
3021 &[
3022 DiagnosticEntry {
3023 range: Point::new(0, 4)..Point::new(0, 7),
3024 diagnostic: Diagnostic {
3025 group_id: 2,
3026 message: "message 1".to_string(),
3027 severity: lsp::DiagnosticSeverity::ERROR,
3028 is_primary: true,
3029 ..Default::default()
3030 }
3031 },
3032 DiagnosticEntry {
3033 range: Point::new(0, 10)..Point::new(0, 13),
3034 diagnostic: Diagnostic {
3035 group_id: 3,
3036 severity: lsp::DiagnosticSeverity::WARNING,
3037 message: "message 2".to_string(),
3038 is_primary: true,
3039 ..Default::default()
3040 }
3041 }
3042 ]
3043 );
3044 });
3045
3046 // Simulate a language server reporting no errors for a file.
3047 fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
3048 lsp::PublishDiagnosticsParams {
3049 uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
3050 version: None,
3051 diagnostics: vec![],
3052 },
3053 );
3054 deterministic.run_until_parked();
3055 project_a.read_with(cx_a, |project, cx| {
3056 assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
3057 });
3058 project_b.read_with(cx_b, |project, cx| {
3059 assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
3060 });
3061 project_c.read_with(cx_c, |project, cx| {
3062 assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
3063 });
3064}
3065
3066#[gpui::test(iterations = 10)]
3067async fn test_collaborating_with_completion(
3068 deterministic: Arc<Deterministic>,
3069 cx_a: &mut TestAppContext,
3070 cx_b: &mut TestAppContext,
3071) {
3072 deterministic.forbid_parking();
3073 let mut server = TestServer::start(&deterministic).await;
3074 let client_a = server.create_client(cx_a, "user_a").await;
3075 let client_b = server.create_client(cx_b, "user_b").await;
3076 server
3077 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3078 .await;
3079 let active_call_a = cx_a.read(ActiveCall::global);
3080
3081 // Set up a fake language server.
3082 let mut language = Language::new(
3083 LanguageConfig {
3084 name: "Rust".into(),
3085 path_suffixes: vec!["rs".to_string()],
3086 ..Default::default()
3087 },
3088 Some(tree_sitter_rust::language()),
3089 );
3090 let mut fake_language_servers = language
3091 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3092 capabilities: lsp::ServerCapabilities {
3093 completion_provider: Some(lsp::CompletionOptions {
3094 trigger_characters: Some(vec![".".to_string()]),
3095 ..Default::default()
3096 }),
3097 ..Default::default()
3098 },
3099 ..Default::default()
3100 }))
3101 .await;
3102 client_a.language_registry.add(Arc::new(language));
3103
3104 client_a
3105 .fs
3106 .insert_tree(
3107 "/a",
3108 json!({
3109 "main.rs": "fn main() { a }",
3110 "other.rs": "",
3111 }),
3112 )
3113 .await;
3114 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3115 let project_id = active_call_a
3116 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3117 .await
3118 .unwrap();
3119 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3120
3121 // Open a file in an editor as the guest.
3122 let buffer_b = project_b
3123 .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
3124 .await
3125 .unwrap();
3126 let (_, window_b) = cx_b.add_window(|_| EmptyView);
3127 let editor_b = cx_b.add_view(&window_b, |cx| {
3128 Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
3129 });
3130
3131 let fake_language_server = fake_language_servers.next().await.unwrap();
3132 cx_a.foreground().run_until_parked();
3133 buffer_b.read_with(cx_b, |buffer, _| {
3134 assert!(!buffer.completion_triggers().is_empty())
3135 });
3136
3137 // Type a completion trigger character as the guest.
3138 editor_b.update(cx_b, |editor, cx| {
3139 editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
3140 editor.handle_input(".", cx);
3141 cx.focus(&editor_b);
3142 });
3143
3144 // Receive a completion request as the host's language server.
3145 // Return some completions from the host's language server.
3146 cx_a.foreground().start_waiting();
3147 fake_language_server
3148 .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
3149 assert_eq!(
3150 params.text_document_position.text_document.uri,
3151 lsp::Url::from_file_path("/a/main.rs").unwrap(),
3152 );
3153 assert_eq!(
3154 params.text_document_position.position,
3155 lsp::Position::new(0, 14),
3156 );
3157
3158 Ok(Some(lsp::CompletionResponse::Array(vec![
3159 lsp::CompletionItem {
3160 label: "first_method(…)".into(),
3161 detail: Some("fn(&mut self, B) -> C".into()),
3162 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
3163 new_text: "first_method($1)".to_string(),
3164 range: lsp::Range::new(
3165 lsp::Position::new(0, 14),
3166 lsp::Position::new(0, 14),
3167 ),
3168 })),
3169 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
3170 ..Default::default()
3171 },
3172 lsp::CompletionItem {
3173 label: "second_method(…)".into(),
3174 detail: Some("fn(&mut self, C) -> D<E>".into()),
3175 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
3176 new_text: "second_method()".to_string(),
3177 range: lsp::Range::new(
3178 lsp::Position::new(0, 14),
3179 lsp::Position::new(0, 14),
3180 ),
3181 })),
3182 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
3183 ..Default::default()
3184 },
3185 ])))
3186 })
3187 .next()
3188 .await
3189 .unwrap();
3190 cx_a.foreground().finish_waiting();
3191
3192 // Open the buffer on the host.
3193 let buffer_a = project_a
3194 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
3195 .await
3196 .unwrap();
3197 cx_a.foreground().run_until_parked();
3198 buffer_a.read_with(cx_a, |buffer, _| {
3199 assert_eq!(buffer.text(), "fn main() { a. }")
3200 });
3201
3202 // Confirm a completion on the guest.
3203 editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
3204 editor_b.update(cx_b, |editor, cx| {
3205 editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
3206 assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
3207 });
3208
3209 // Return a resolved completion from the host's language server.
3210 // The resolved completion has an additional text edit.
3211 fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
3212 |params, _| async move {
3213 assert_eq!(params.label, "first_method(…)");
3214 Ok(lsp::CompletionItem {
3215 label: "first_method(…)".into(),
3216 detail: Some("fn(&mut self, B) -> C".into()),
3217 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
3218 new_text: "first_method($1)".to_string(),
3219 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
3220 })),
3221 additional_text_edits: Some(vec![lsp::TextEdit {
3222 new_text: "use d::SomeTrait;\n".to_string(),
3223 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
3224 }]),
3225 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
3226 ..Default::default()
3227 })
3228 },
3229 );
3230
3231 // The additional edit is applied.
3232 cx_a.foreground().run_until_parked();
3233 buffer_a.read_with(cx_a, |buffer, _| {
3234 assert_eq!(
3235 buffer.text(),
3236 "use d::SomeTrait;\nfn main() { a.first_method() }"
3237 );
3238 });
3239 buffer_b.read_with(cx_b, |buffer, _| {
3240 assert_eq!(
3241 buffer.text(),
3242 "use d::SomeTrait;\nfn main() { a.first_method() }"
3243 );
3244 });
3245}
3246
3247#[gpui::test(iterations = 10)]
3248async fn test_reloading_buffer_manually(
3249 deterministic: Arc<Deterministic>,
3250 cx_a: &mut TestAppContext,
3251 cx_b: &mut TestAppContext,
3252) {
3253 deterministic.forbid_parking();
3254 let mut server = TestServer::start(&deterministic).await;
3255 let client_a = server.create_client(cx_a, "user_a").await;
3256 let client_b = server.create_client(cx_b, "user_b").await;
3257 server
3258 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3259 .await;
3260 let active_call_a = cx_a.read(ActiveCall::global);
3261
3262 client_a
3263 .fs
3264 .insert_tree("/a", json!({ "a.rs": "let one = 1;" }))
3265 .await;
3266 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3267 let buffer_a = project_a
3268 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
3269 .await
3270 .unwrap();
3271 let project_id = active_call_a
3272 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3273 .await
3274 .unwrap();
3275
3276 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3277
3278 let buffer_b = cx_b
3279 .background()
3280 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3281 .await
3282 .unwrap();
3283 buffer_b.update(cx_b, |buffer, cx| {
3284 buffer.edit([(4..7, "six")], None, cx);
3285 buffer.edit([(10..11, "6")], None, cx);
3286 assert_eq!(buffer.text(), "let six = 6;");
3287 assert!(buffer.is_dirty());
3288 assert!(!buffer.has_conflict());
3289 });
3290 cx_a.foreground().run_until_parked();
3291 buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
3292
3293 client_a
3294 .fs
3295 .save(
3296 "/a/a.rs".as_ref(),
3297 &Rope::from("let seven = 7;"),
3298 LineEnding::Unix,
3299 )
3300 .await
3301 .unwrap();
3302 cx_a.foreground().run_until_parked();
3303 buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
3304 buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
3305
3306 project_b
3307 .update(cx_b, |project, cx| {
3308 project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
3309 })
3310 .await
3311 .unwrap();
3312 buffer_a.read_with(cx_a, |buffer, _| {
3313 assert_eq!(buffer.text(), "let seven = 7;");
3314 assert!(!buffer.is_dirty());
3315 assert!(!buffer.has_conflict());
3316 });
3317 buffer_b.read_with(cx_b, |buffer, _| {
3318 assert_eq!(buffer.text(), "let seven = 7;");
3319 assert!(!buffer.is_dirty());
3320 assert!(!buffer.has_conflict());
3321 });
3322
3323 buffer_a.update(cx_a, |buffer, cx| {
3324 // Undoing on the host is a no-op when the reload was initiated by the guest.
3325 buffer.undo(cx);
3326 assert_eq!(buffer.text(), "let seven = 7;");
3327 assert!(!buffer.is_dirty());
3328 assert!(!buffer.has_conflict());
3329 });
3330 buffer_b.update(cx_b, |buffer, cx| {
3331 // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
3332 buffer.undo(cx);
3333 assert_eq!(buffer.text(), "let six = 6;");
3334 assert!(buffer.is_dirty());
3335 assert!(!buffer.has_conflict());
3336 });
3337}
3338
3339#[gpui::test(iterations = 10)]
3340async fn test_formatting_buffer(
3341 deterministic: Arc<Deterministic>,
3342 cx_a: &mut TestAppContext,
3343 cx_b: &mut TestAppContext,
3344) {
3345 use project::FormatTrigger;
3346
3347 let mut server = TestServer::start(&deterministic).await;
3348 let client_a = server.create_client(cx_a, "user_a").await;
3349 let client_b = server.create_client(cx_b, "user_b").await;
3350 server
3351 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3352 .await;
3353 let active_call_a = cx_a.read(ActiveCall::global);
3354
3355 // Set up a fake language server.
3356 let mut language = Language::new(
3357 LanguageConfig {
3358 name: "Rust".into(),
3359 path_suffixes: vec!["rs".to_string()],
3360 ..Default::default()
3361 },
3362 Some(tree_sitter_rust::language()),
3363 );
3364 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3365 client_a.language_registry.add(Arc::new(language));
3366
3367 // Here we insert a fake tree with a directory that exists on disk. This is needed
3368 // because later we'll invoke a command, which requires passing a working directory
3369 // that points to a valid location on disk.
3370 let directory = env::current_dir().unwrap();
3371 client_a
3372 .fs
3373 .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
3374 .await;
3375 let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
3376 let project_id = active_call_a
3377 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3378 .await
3379 .unwrap();
3380 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3381
3382 let buffer_b = cx_b
3383 .background()
3384 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3385 .await
3386 .unwrap();
3387
3388 let fake_language_server = fake_language_servers.next().await.unwrap();
3389 fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
3390 Ok(Some(vec![
3391 lsp::TextEdit {
3392 range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
3393 new_text: "h".to_string(),
3394 },
3395 lsp::TextEdit {
3396 range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
3397 new_text: "y".to_string(),
3398 },
3399 ]))
3400 });
3401
3402 project_b
3403 .update(cx_b, |project, cx| {
3404 project.format(
3405 HashSet::from_iter([buffer_b.clone()]),
3406 true,
3407 FormatTrigger::Save,
3408 cx,
3409 )
3410 })
3411 .await
3412 .unwrap();
3413 assert_eq!(
3414 buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
3415 "let honey = \"two\""
3416 );
3417
3418 // Ensure buffer can be formatted using an external command. Notice how the
3419 // host's configuration is honored as opposed to using the guest's settings.
3420 cx_a.update(|cx| {
3421 cx.update_global(|settings: &mut Settings, _| {
3422 settings.editor_defaults.formatter = Some(Formatter::External {
3423 command: "awk".to_string(),
3424 arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
3425 });
3426 });
3427 });
3428 project_b
3429 .update(cx_b, |project, cx| {
3430 project.format(
3431 HashSet::from_iter([buffer_b.clone()]),
3432 true,
3433 FormatTrigger::Save,
3434 cx,
3435 )
3436 })
3437 .await
3438 .unwrap();
3439 assert_eq!(
3440 buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
3441 format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
3442 );
3443}
3444
3445#[gpui::test(iterations = 10)]
3446async fn test_definition(
3447 deterministic: Arc<Deterministic>,
3448 cx_a: &mut TestAppContext,
3449 cx_b: &mut TestAppContext,
3450) {
3451 deterministic.forbid_parking();
3452 let mut server = TestServer::start(&deterministic).await;
3453 let client_a = server.create_client(cx_a, "user_a").await;
3454 let client_b = server.create_client(cx_b, "user_b").await;
3455 server
3456 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3457 .await;
3458 let active_call_a = cx_a.read(ActiveCall::global);
3459
3460 // Set up a fake language server.
3461 let mut language = Language::new(
3462 LanguageConfig {
3463 name: "Rust".into(),
3464 path_suffixes: vec!["rs".to_string()],
3465 ..Default::default()
3466 },
3467 Some(tree_sitter_rust::language()),
3468 );
3469 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3470 client_a.language_registry.add(Arc::new(language));
3471
3472 client_a
3473 .fs
3474 .insert_tree(
3475 "/root",
3476 json!({
3477 "dir-1": {
3478 "a.rs": "const ONE: usize = b::TWO + b::THREE;",
3479 },
3480 "dir-2": {
3481 "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
3482 "c.rs": "type T2 = usize;",
3483 }
3484 }),
3485 )
3486 .await;
3487 let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
3488 let project_id = active_call_a
3489 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3490 .await
3491 .unwrap();
3492 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3493
3494 // Open the file on client B.
3495 let buffer_b = cx_b
3496 .background()
3497 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3498 .await
3499 .unwrap();
3500
3501 // Request the definition of a symbol as the guest.
3502 let fake_language_server = fake_language_servers.next().await.unwrap();
3503 fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
3504 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
3505 lsp::Location::new(
3506 lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
3507 lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
3508 ),
3509 )))
3510 });
3511
3512 let definitions_1 = project_b
3513 .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
3514 .await
3515 .unwrap();
3516 cx_b.read(|cx| {
3517 assert_eq!(definitions_1.len(), 1);
3518 assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
3519 let target_buffer = definitions_1[0].target.buffer.read(cx);
3520 assert_eq!(
3521 target_buffer.text(),
3522 "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
3523 );
3524 assert_eq!(
3525 definitions_1[0].target.range.to_point(target_buffer),
3526 Point::new(0, 6)..Point::new(0, 9)
3527 );
3528 });
3529
3530 // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
3531 // the previous call to `definition`.
3532 fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
3533 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
3534 lsp::Location::new(
3535 lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
3536 lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
3537 ),
3538 )))
3539 });
3540
3541 let definitions_2 = project_b
3542 .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
3543 .await
3544 .unwrap();
3545 cx_b.read(|cx| {
3546 assert_eq!(definitions_2.len(), 1);
3547 assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
3548 let target_buffer = definitions_2[0].target.buffer.read(cx);
3549 assert_eq!(
3550 target_buffer.text(),
3551 "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
3552 );
3553 assert_eq!(
3554 definitions_2[0].target.range.to_point(target_buffer),
3555 Point::new(1, 6)..Point::new(1, 11)
3556 );
3557 });
3558 assert_eq!(
3559 definitions_1[0].target.buffer,
3560 definitions_2[0].target.buffer
3561 );
3562
3563 fake_language_server.handle_request::<lsp::request::GotoTypeDefinition, _, _>(
3564 |req, _| async move {
3565 assert_eq!(
3566 req.text_document_position_params.position,
3567 lsp::Position::new(0, 7)
3568 );
3569 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
3570 lsp::Location::new(
3571 lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
3572 lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
3573 ),
3574 )))
3575 },
3576 );
3577
3578 let type_definitions = project_b
3579 .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
3580 .await
3581 .unwrap();
3582 cx_b.read(|cx| {
3583 assert_eq!(type_definitions.len(), 1);
3584 let target_buffer = type_definitions[0].target.buffer.read(cx);
3585 assert_eq!(target_buffer.text(), "type T2 = usize;");
3586 assert_eq!(
3587 type_definitions[0].target.range.to_point(target_buffer),
3588 Point::new(0, 5)..Point::new(0, 7)
3589 );
3590 });
3591}
3592
3593#[gpui::test(iterations = 10)]
3594async fn test_references(
3595 deterministic: Arc<Deterministic>,
3596 cx_a: &mut TestAppContext,
3597 cx_b: &mut TestAppContext,
3598) {
3599 deterministic.forbid_parking();
3600 let mut server = TestServer::start(&deterministic).await;
3601 let client_a = server.create_client(cx_a, "user_a").await;
3602 let client_b = server.create_client(cx_b, "user_b").await;
3603 server
3604 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3605 .await;
3606 let active_call_a = cx_a.read(ActiveCall::global);
3607
3608 // Set up a fake language server.
3609 let mut language = Language::new(
3610 LanguageConfig {
3611 name: "Rust".into(),
3612 path_suffixes: vec!["rs".to_string()],
3613 ..Default::default()
3614 },
3615 Some(tree_sitter_rust::language()),
3616 );
3617 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3618 client_a.language_registry.add(Arc::new(language));
3619
3620 client_a
3621 .fs
3622 .insert_tree(
3623 "/root",
3624 json!({
3625 "dir-1": {
3626 "one.rs": "const ONE: usize = 1;",
3627 "two.rs": "const TWO: usize = one::ONE + one::ONE;",
3628 },
3629 "dir-2": {
3630 "three.rs": "const THREE: usize = two::TWO + one::ONE;",
3631 }
3632 }),
3633 )
3634 .await;
3635 let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
3636 let project_id = active_call_a
3637 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3638 .await
3639 .unwrap();
3640 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3641
3642 // Open the file on client B.
3643 let buffer_b = cx_b
3644 .background()
3645 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
3646 .await
3647 .unwrap();
3648
3649 // Request references to a symbol as the guest.
3650 let fake_language_server = fake_language_servers.next().await.unwrap();
3651 fake_language_server.handle_request::<lsp::request::References, _, _>(|params, _| async move {
3652 assert_eq!(
3653 params.text_document_position.text_document.uri.as_str(),
3654 "file:///root/dir-1/one.rs"
3655 );
3656 Ok(Some(vec![
3657 lsp::Location {
3658 uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
3659 range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
3660 },
3661 lsp::Location {
3662 uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
3663 range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
3664 },
3665 lsp::Location {
3666 uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
3667 range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
3668 },
3669 ]))
3670 });
3671
3672 let references = project_b
3673 .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
3674 .await
3675 .unwrap();
3676 cx_b.read(|cx| {
3677 assert_eq!(references.len(), 3);
3678 assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
3679
3680 let two_buffer = references[0].buffer.read(cx);
3681 let three_buffer = references[2].buffer.read(cx);
3682 assert_eq!(
3683 two_buffer.file().unwrap().path().as_ref(),
3684 Path::new("two.rs")
3685 );
3686 assert_eq!(references[1].buffer, references[0].buffer);
3687 assert_eq!(
3688 three_buffer.file().unwrap().full_path(cx),
3689 Path::new("/root/dir-2/three.rs")
3690 );
3691
3692 assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
3693 assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
3694 assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
3695 });
3696}
3697
3698#[gpui::test(iterations = 10)]
3699async fn test_project_search(
3700 deterministic: Arc<Deterministic>,
3701 cx_a: &mut TestAppContext,
3702 cx_b: &mut TestAppContext,
3703) {
3704 deterministic.forbid_parking();
3705 let mut server = TestServer::start(&deterministic).await;
3706 let client_a = server.create_client(cx_a, "user_a").await;
3707 let client_b = server.create_client(cx_b, "user_b").await;
3708 server
3709 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3710 .await;
3711 let active_call_a = cx_a.read(ActiveCall::global);
3712
3713 client_a
3714 .fs
3715 .insert_tree(
3716 "/root",
3717 json!({
3718 "dir-1": {
3719 "a": "hello world",
3720 "b": "goodnight moon",
3721 "c": "a world of goo",
3722 "d": "world champion of clown world",
3723 },
3724 "dir-2": {
3725 "e": "disney world is fun",
3726 }
3727 }),
3728 )
3729 .await;
3730 let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
3731 let (worktree_2, _) = project_a
3732 .update(cx_a, |p, cx| {
3733 p.find_or_create_local_worktree("/root/dir-2", true, cx)
3734 })
3735 .await
3736 .unwrap();
3737 worktree_2
3738 .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
3739 .await;
3740 let project_id = active_call_a
3741 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3742 .await
3743 .unwrap();
3744
3745 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3746
3747 // Perform a search as the guest.
3748 let results = project_b
3749 .update(cx_b, |project, cx| {
3750 project.search(SearchQuery::text("world", false, false), cx)
3751 })
3752 .await
3753 .unwrap();
3754
3755 let mut ranges_by_path = results
3756 .into_iter()
3757 .map(|(buffer, ranges)| {
3758 buffer.read_with(cx_b, |buffer, cx| {
3759 let path = buffer.file().unwrap().full_path(cx);
3760 let offset_ranges = ranges
3761 .into_iter()
3762 .map(|range| range.to_offset(buffer))
3763 .collect::<Vec<_>>();
3764 (path, offset_ranges)
3765 })
3766 })
3767 .collect::<Vec<_>>();
3768 ranges_by_path.sort_by_key(|(path, _)| path.clone());
3769
3770 assert_eq!(
3771 ranges_by_path,
3772 &[
3773 (PathBuf::from("dir-1/a"), vec![6..11]),
3774 (PathBuf::from("dir-1/c"), vec![2..7]),
3775 (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
3776 (PathBuf::from("dir-2/e"), vec![7..12]),
3777 ]
3778 );
3779}
3780
3781#[gpui::test(iterations = 10)]
3782async fn test_document_highlights(
3783 deterministic: Arc<Deterministic>,
3784 cx_a: &mut TestAppContext,
3785 cx_b: &mut TestAppContext,
3786) {
3787 deterministic.forbid_parking();
3788 let mut server = TestServer::start(&deterministic).await;
3789 let client_a = server.create_client(cx_a, "user_a").await;
3790 let client_b = server.create_client(cx_b, "user_b").await;
3791 server
3792 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3793 .await;
3794 let active_call_a = cx_a.read(ActiveCall::global);
3795
3796 client_a
3797 .fs
3798 .insert_tree(
3799 "/root-1",
3800 json!({
3801 "main.rs": "fn double(number: i32) -> i32 { number + number }",
3802 }),
3803 )
3804 .await;
3805
3806 // Set up a fake language server.
3807 let mut language = Language::new(
3808 LanguageConfig {
3809 name: "Rust".into(),
3810 path_suffixes: vec!["rs".to_string()],
3811 ..Default::default()
3812 },
3813 Some(tree_sitter_rust::language()),
3814 );
3815 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3816 client_a.language_registry.add(Arc::new(language));
3817
3818 let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
3819 let project_id = active_call_a
3820 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3821 .await
3822 .unwrap();
3823 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3824
3825 // Open the file on client B.
3826 let buffer_b = cx_b
3827 .background()
3828 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
3829 .await
3830 .unwrap();
3831
3832 // Request document highlights as the guest.
3833 let fake_language_server = fake_language_servers.next().await.unwrap();
3834 fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
3835 |params, _| async move {
3836 assert_eq!(
3837 params
3838 .text_document_position_params
3839 .text_document
3840 .uri
3841 .as_str(),
3842 "file:///root-1/main.rs"
3843 );
3844 assert_eq!(
3845 params.text_document_position_params.position,
3846 lsp::Position::new(0, 34)
3847 );
3848 Ok(Some(vec![
3849 lsp::DocumentHighlight {
3850 kind: Some(lsp::DocumentHighlightKind::WRITE),
3851 range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
3852 },
3853 lsp::DocumentHighlight {
3854 kind: Some(lsp::DocumentHighlightKind::READ),
3855 range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
3856 },
3857 lsp::DocumentHighlight {
3858 kind: Some(lsp::DocumentHighlightKind::READ),
3859 range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
3860 },
3861 ]))
3862 },
3863 );
3864
3865 let highlights = project_b
3866 .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
3867 .await
3868 .unwrap();
3869 buffer_b.read_with(cx_b, |buffer, _| {
3870 let snapshot = buffer.snapshot();
3871
3872 let highlights = highlights
3873 .into_iter()
3874 .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
3875 .collect::<Vec<_>>();
3876 assert_eq!(
3877 highlights,
3878 &[
3879 (lsp::DocumentHighlightKind::WRITE, 10..16),
3880 (lsp::DocumentHighlightKind::READ, 32..38),
3881 (lsp::DocumentHighlightKind::READ, 41..47)
3882 ]
3883 )
3884 });
3885}
3886
3887#[gpui::test(iterations = 10)]
3888async fn test_lsp_hover(
3889 deterministic: Arc<Deterministic>,
3890 cx_a: &mut TestAppContext,
3891 cx_b: &mut TestAppContext,
3892) {
3893 deterministic.forbid_parking();
3894 let mut server = TestServer::start(&deterministic).await;
3895 let client_a = server.create_client(cx_a, "user_a").await;
3896 let client_b = server.create_client(cx_b, "user_b").await;
3897 server
3898 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3899 .await;
3900 let active_call_a = cx_a.read(ActiveCall::global);
3901
3902 client_a
3903 .fs
3904 .insert_tree(
3905 "/root-1",
3906 json!({
3907 "main.rs": "use std::collections::HashMap;",
3908 }),
3909 )
3910 .await;
3911
3912 // Set up a fake language server.
3913 let mut language = Language::new(
3914 LanguageConfig {
3915 name: "Rust".into(),
3916 path_suffixes: vec!["rs".to_string()],
3917 ..Default::default()
3918 },
3919 Some(tree_sitter_rust::language()),
3920 );
3921 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3922 client_a.language_registry.add(Arc::new(language));
3923
3924 let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
3925 let project_id = active_call_a
3926 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3927 .await
3928 .unwrap();
3929 let project_b = client_b.build_remote_project(project_id, cx_b).await;
3930
3931 // Open the file as the guest
3932 let buffer_b = cx_b
3933 .background()
3934 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
3935 .await
3936 .unwrap();
3937
3938 // Request hover information as the guest.
3939 let fake_language_server = fake_language_servers.next().await.unwrap();
3940 fake_language_server.handle_request::<lsp::request::HoverRequest, _, _>(
3941 |params, _| async move {
3942 assert_eq!(
3943 params
3944 .text_document_position_params
3945 .text_document
3946 .uri
3947 .as_str(),
3948 "file:///root-1/main.rs"
3949 );
3950 assert_eq!(
3951 params.text_document_position_params.position,
3952 lsp::Position::new(0, 22)
3953 );
3954 Ok(Some(lsp::Hover {
3955 contents: lsp::HoverContents::Array(vec![
3956 lsp::MarkedString::String("Test hover content.".to_string()),
3957 lsp::MarkedString::LanguageString(lsp::LanguageString {
3958 language: "Rust".to_string(),
3959 value: "let foo = 42;".to_string(),
3960 }),
3961 ]),
3962 range: Some(lsp::Range::new(
3963 lsp::Position::new(0, 22),
3964 lsp::Position::new(0, 29),
3965 )),
3966 }))
3967 },
3968 );
3969
3970 let hover_info = project_b
3971 .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
3972 .await
3973 .unwrap()
3974 .unwrap();
3975 buffer_b.read_with(cx_b, |buffer, _| {
3976 let snapshot = buffer.snapshot();
3977 assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29);
3978 assert_eq!(
3979 hover_info.contents,
3980 vec![
3981 project::HoverBlock {
3982 text: "Test hover content.".to_string(),
3983 language: None,
3984 },
3985 project::HoverBlock {
3986 text: "let foo = 42;".to_string(),
3987 language: Some("Rust".to_string()),
3988 }
3989 ]
3990 );
3991 });
3992}
3993
3994#[gpui::test(iterations = 10)]
3995async fn test_project_symbols(
3996 deterministic: Arc<Deterministic>,
3997 cx_a: &mut TestAppContext,
3998 cx_b: &mut TestAppContext,
3999) {
4000 deterministic.forbid_parking();
4001 let mut server = TestServer::start(&deterministic).await;
4002 let client_a = server.create_client(cx_a, "user_a").await;
4003 let client_b = server.create_client(cx_b, "user_b").await;
4004 server
4005 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4006 .await;
4007 let active_call_a = cx_a.read(ActiveCall::global);
4008
4009 // Set up a fake language server.
4010 let mut language = Language::new(
4011 LanguageConfig {
4012 name: "Rust".into(),
4013 path_suffixes: vec!["rs".to_string()],
4014 ..Default::default()
4015 },
4016 Some(tree_sitter_rust::language()),
4017 );
4018 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
4019 client_a.language_registry.add(Arc::new(language));
4020
4021 client_a
4022 .fs
4023 .insert_tree(
4024 "/code",
4025 json!({
4026 "crate-1": {
4027 "one.rs": "const ONE: usize = 1;",
4028 },
4029 "crate-2": {
4030 "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
4031 },
4032 "private": {
4033 "passwords.txt": "the-password",
4034 }
4035 }),
4036 )
4037 .await;
4038 let (project_a, worktree_id) = client_a.build_local_project("/code/crate-1", cx_a).await;
4039 let project_id = active_call_a
4040 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4041 .await
4042 .unwrap();
4043 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4044
4045 // Cause the language server to start.
4046 let _buffer = cx_b
4047 .background()
4048 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
4049 .await
4050 .unwrap();
4051
4052 let fake_language_server = fake_language_servers.next().await.unwrap();
4053 fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
4054 #[allow(deprecated)]
4055 Ok(Some(vec![lsp::SymbolInformation {
4056 name: "TWO".into(),
4057 location: lsp::Location {
4058 uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
4059 range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4060 },
4061 kind: lsp::SymbolKind::CONSTANT,
4062 tags: None,
4063 container_name: None,
4064 deprecated: None,
4065 }]))
4066 });
4067
4068 // Request the definition of a symbol as the guest.
4069 let symbols = project_b
4070 .update(cx_b, |p, cx| p.symbols("two", cx))
4071 .await
4072 .unwrap();
4073 assert_eq!(symbols.len(), 1);
4074 assert_eq!(symbols[0].name, "TWO");
4075
4076 // Open one of the returned symbols.
4077 let buffer_b_2 = project_b
4078 .update(cx_b, |project, cx| {
4079 project.open_buffer_for_symbol(&symbols[0], cx)
4080 })
4081 .await
4082 .unwrap();
4083 buffer_b_2.read_with(cx_b, |buffer, _| {
4084 assert_eq!(
4085 buffer.file().unwrap().path().as_ref(),
4086 Path::new("../crate-2/two.rs")
4087 );
4088 });
4089
4090 // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
4091 let mut fake_symbol = symbols[0].clone();
4092 fake_symbol.path.path = Path::new("/code/secrets").into();
4093 let error = project_b
4094 .update(cx_b, |project, cx| {
4095 project.open_buffer_for_symbol(&fake_symbol, cx)
4096 })
4097 .await
4098 .unwrap_err();
4099 assert!(error.to_string().contains("invalid symbol signature"));
4100}
4101
4102#[gpui::test(iterations = 10)]
4103async fn test_open_buffer_while_getting_definition_pointing_to_it(
4104 deterministic: Arc<Deterministic>,
4105 cx_a: &mut TestAppContext,
4106 cx_b: &mut TestAppContext,
4107 mut rng: StdRng,
4108) {
4109 deterministic.forbid_parking();
4110 let mut server = TestServer::start(&deterministic).await;
4111 let client_a = server.create_client(cx_a, "user_a").await;
4112 let client_b = server.create_client(cx_b, "user_b").await;
4113 server
4114 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4115 .await;
4116 let active_call_a = cx_a.read(ActiveCall::global);
4117
4118 // Set up a fake language server.
4119 let mut language = Language::new(
4120 LanguageConfig {
4121 name: "Rust".into(),
4122 path_suffixes: vec!["rs".to_string()],
4123 ..Default::default()
4124 },
4125 Some(tree_sitter_rust::language()),
4126 );
4127 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
4128 client_a.language_registry.add(Arc::new(language));
4129
4130 client_a
4131 .fs
4132 .insert_tree(
4133 "/root",
4134 json!({
4135 "a.rs": "const ONE: usize = b::TWO;",
4136 "b.rs": "const TWO: usize = 2",
4137 }),
4138 )
4139 .await;
4140 let (project_a, worktree_id) = client_a.build_local_project("/root", cx_a).await;
4141 let project_id = active_call_a
4142 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4143 .await
4144 .unwrap();
4145 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4146
4147 let buffer_b1 = cx_b
4148 .background()
4149 .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
4150 .await
4151 .unwrap();
4152
4153 let fake_language_server = fake_language_servers.next().await.unwrap();
4154 fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
4155 Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4156 lsp::Location::new(
4157 lsp::Url::from_file_path("/root/b.rs").unwrap(),
4158 lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4159 ),
4160 )))
4161 });
4162
4163 let definitions;
4164 let buffer_b2;
4165 if rng.gen() {
4166 definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
4167 buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
4168 } else {
4169 buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
4170 definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
4171 }
4172
4173 let buffer_b2 = buffer_b2.await.unwrap();
4174 let definitions = definitions.await.unwrap();
4175 assert_eq!(definitions.len(), 1);
4176 assert_eq!(definitions[0].target.buffer, buffer_b2);
4177}
4178
4179#[gpui::test(iterations = 10)]
4180async fn test_collaborating_with_code_actions(
4181 deterministic: Arc<Deterministic>,
4182 cx_a: &mut TestAppContext,
4183 cx_b: &mut TestAppContext,
4184) {
4185 deterministic.forbid_parking();
4186 cx_b.update(editor::init);
4187 let mut server = TestServer::start(&deterministic).await;
4188 let client_a = server.create_client(cx_a, "user_a").await;
4189 let client_b = server.create_client(cx_b, "user_b").await;
4190 server
4191 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4192 .await;
4193 let active_call_a = cx_a.read(ActiveCall::global);
4194
4195 // Set up a fake language server.
4196 let mut language = Language::new(
4197 LanguageConfig {
4198 name: "Rust".into(),
4199 path_suffixes: vec!["rs".to_string()],
4200 ..Default::default()
4201 },
4202 Some(tree_sitter_rust::language()),
4203 );
4204 let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
4205 client_a.language_registry.add(Arc::new(language));
4206
4207 client_a
4208 .fs
4209 .insert_tree(
4210 "/a",
4211 json!({
4212 "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
4213 "other.rs": "pub fn foo() -> usize { 4 }",
4214 }),
4215 )
4216 .await;
4217 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4218 let project_id = active_call_a
4219 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4220 .await
4221 .unwrap();
4222
4223 // Join the project as client B.
4224 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4225 let (_window_b, workspace_b) = cx_b.add_window(|cx| {
4226 Workspace::new(
4227 Default::default(),
4228 0,
4229 project_b.clone(),
4230 |_, _| unimplemented!(),
4231 cx,
4232 )
4233 });
4234 let editor_b = workspace_b
4235 .update(cx_b, |workspace, cx| {
4236 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
4237 })
4238 .await
4239 .unwrap()
4240 .downcast::<Editor>()
4241 .unwrap();
4242
4243 let mut fake_language_server = fake_language_servers.next().await.unwrap();
4244 fake_language_server
4245 .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
4246 assert_eq!(
4247 params.text_document.uri,
4248 lsp::Url::from_file_path("/a/main.rs").unwrap(),
4249 );
4250 assert_eq!(params.range.start, lsp::Position::new(0, 0));
4251 assert_eq!(params.range.end, lsp::Position::new(0, 0));
4252 Ok(None)
4253 })
4254 .next()
4255 .await;
4256
4257 // Move cursor to a location that contains code actions.
4258 editor_b.update(cx_b, |editor, cx| {
4259 editor.change_selections(None, cx, |s| {
4260 s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
4261 });
4262 cx.focus(&editor_b);
4263 });
4264
4265 fake_language_server
4266 .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
4267 assert_eq!(
4268 params.text_document.uri,
4269 lsp::Url::from_file_path("/a/main.rs").unwrap(),
4270 );
4271 assert_eq!(params.range.start, lsp::Position::new(1, 31));
4272 assert_eq!(params.range.end, lsp::Position::new(1, 31));
4273
4274 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
4275 lsp::CodeAction {
4276 title: "Inline into all callers".to_string(),
4277 edit: Some(lsp::WorkspaceEdit {
4278 changes: Some(
4279 [
4280 (
4281 lsp::Url::from_file_path("/a/main.rs").unwrap(),
4282 vec![lsp::TextEdit::new(
4283 lsp::Range::new(
4284 lsp::Position::new(1, 22),
4285 lsp::Position::new(1, 34),
4286 ),
4287 "4".to_string(),
4288 )],
4289 ),
4290 (
4291 lsp::Url::from_file_path("/a/other.rs").unwrap(),
4292 vec![lsp::TextEdit::new(
4293 lsp::Range::new(
4294 lsp::Position::new(0, 0),
4295 lsp::Position::new(0, 27),
4296 ),
4297 "".to_string(),
4298 )],
4299 ),
4300 ]
4301 .into_iter()
4302 .collect(),
4303 ),
4304 ..Default::default()
4305 }),
4306 data: Some(json!({
4307 "codeActionParams": {
4308 "range": {
4309 "start": {"line": 1, "column": 31},
4310 "end": {"line": 1, "column": 31},
4311 }
4312 }
4313 })),
4314 ..Default::default()
4315 },
4316 )]))
4317 })
4318 .next()
4319 .await;
4320
4321 // Toggle code actions and wait for them to display.
4322 editor_b.update(cx_b, |editor, cx| {
4323 editor.toggle_code_actions(
4324 &ToggleCodeActions {
4325 deployed_from_indicator: false,
4326 },
4327 cx,
4328 );
4329 });
4330 cx_a.foreground().run_until_parked();
4331 editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
4332
4333 fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
4334
4335 // Confirming the code action will trigger a resolve request.
4336 let confirm_action = workspace_b
4337 .update(cx_b, |workspace, cx| {
4338 Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
4339 })
4340 .unwrap();
4341 fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
4342 |_, _| async move {
4343 Ok(lsp::CodeAction {
4344 title: "Inline into all callers".to_string(),
4345 edit: Some(lsp::WorkspaceEdit {
4346 changes: Some(
4347 [
4348 (
4349 lsp::Url::from_file_path("/a/main.rs").unwrap(),
4350 vec![lsp::TextEdit::new(
4351 lsp::Range::new(
4352 lsp::Position::new(1, 22),
4353 lsp::Position::new(1, 34),
4354 ),
4355 "4".to_string(),
4356 )],
4357 ),
4358 (
4359 lsp::Url::from_file_path("/a/other.rs").unwrap(),
4360 vec![lsp::TextEdit::new(
4361 lsp::Range::new(
4362 lsp::Position::new(0, 0),
4363 lsp::Position::new(0, 27),
4364 ),
4365 "".to_string(),
4366 )],
4367 ),
4368 ]
4369 .into_iter()
4370 .collect(),
4371 ),
4372 ..Default::default()
4373 }),
4374 ..Default::default()
4375 })
4376 },
4377 );
4378
4379 // After the action is confirmed, an editor containing both modified files is opened.
4380 confirm_action.await.unwrap();
4381 let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
4382 workspace
4383 .active_item(cx)
4384 .unwrap()
4385 .downcast::<Editor>()
4386 .unwrap()
4387 });
4388 code_action_editor.update(cx_b, |editor, cx| {
4389 assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
4390 editor.undo(&Undo, cx);
4391 assert_eq!(
4392 editor.text(cx),
4393 "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
4394 );
4395 editor.redo(&Redo, cx);
4396 assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
4397 });
4398}
4399
4400#[gpui::test(iterations = 10)]
4401async fn test_collaborating_with_renames(
4402 deterministic: Arc<Deterministic>,
4403 cx_a: &mut TestAppContext,
4404 cx_b: &mut TestAppContext,
4405) {
4406 deterministic.forbid_parking();
4407 cx_b.update(editor::init);
4408 let mut server = TestServer::start(&deterministic).await;
4409 let client_a = server.create_client(cx_a, "user_a").await;
4410 let client_b = server.create_client(cx_b, "user_b").await;
4411 server
4412 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4413 .await;
4414 let active_call_a = cx_a.read(ActiveCall::global);
4415
4416 // Set up a fake language server.
4417 let mut language = Language::new(
4418 LanguageConfig {
4419 name: "Rust".into(),
4420 path_suffixes: vec!["rs".to_string()],
4421 ..Default::default()
4422 },
4423 Some(tree_sitter_rust::language()),
4424 );
4425 let mut fake_language_servers = language
4426 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4427 capabilities: lsp::ServerCapabilities {
4428 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
4429 prepare_provider: Some(true),
4430 work_done_progress_options: Default::default(),
4431 })),
4432 ..Default::default()
4433 },
4434 ..Default::default()
4435 }))
4436 .await;
4437 client_a.language_registry.add(Arc::new(language));
4438
4439 client_a
4440 .fs
4441 .insert_tree(
4442 "/dir",
4443 json!({
4444 "one.rs": "const ONE: usize = 1;",
4445 "two.rs": "const TWO: usize = one::ONE + one::ONE;"
4446 }),
4447 )
4448 .await;
4449 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
4450 let project_id = active_call_a
4451 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4452 .await
4453 .unwrap();
4454 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4455
4456 let (_window_b, workspace_b) = cx_b.add_window(|cx| {
4457 Workspace::new(
4458 Default::default(),
4459 0,
4460 project_b.clone(),
4461 |_, _| unimplemented!(),
4462 cx,
4463 )
4464 });
4465 let editor_b = workspace_b
4466 .update(cx_b, |workspace, cx| {
4467 workspace.open_path((worktree_id, "one.rs"), None, true, cx)
4468 })
4469 .await
4470 .unwrap()
4471 .downcast::<Editor>()
4472 .unwrap();
4473 let fake_language_server = fake_language_servers.next().await.unwrap();
4474
4475 // Move cursor to a location that can be renamed.
4476 let prepare_rename = editor_b.update(cx_b, |editor, cx| {
4477 editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
4478 editor.rename(&Rename, cx).unwrap()
4479 });
4480
4481 fake_language_server
4482 .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
4483 assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
4484 assert_eq!(params.position, lsp::Position::new(0, 7));
4485 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
4486 lsp::Position::new(0, 6),
4487 lsp::Position::new(0, 9),
4488 ))))
4489 })
4490 .next()
4491 .await
4492 .unwrap();
4493 prepare_rename.await.unwrap();
4494 editor_b.update(cx_b, |editor, cx| {
4495 let rename = editor.pending_rename().unwrap();
4496 let buffer = editor.buffer().read(cx).snapshot(cx);
4497 assert_eq!(
4498 rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
4499 6..9
4500 );
4501 rename.editor.update(cx, |rename_editor, cx| {
4502 rename_editor.buffer().update(cx, |rename_buffer, cx| {
4503 rename_buffer.edit([(0..3, "THREE")], None, cx);
4504 });
4505 });
4506 });
4507
4508 let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
4509 Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
4510 });
4511 fake_language_server
4512 .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
4513 assert_eq!(
4514 params.text_document_position.text_document.uri.as_str(),
4515 "file:///dir/one.rs"
4516 );
4517 assert_eq!(
4518 params.text_document_position.position,
4519 lsp::Position::new(0, 6)
4520 );
4521 assert_eq!(params.new_name, "THREE");
4522 Ok(Some(lsp::WorkspaceEdit {
4523 changes: Some(
4524 [
4525 (
4526 lsp::Url::from_file_path("/dir/one.rs").unwrap(),
4527 vec![lsp::TextEdit::new(
4528 lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4529 "THREE".to_string(),
4530 )],
4531 ),
4532 (
4533 lsp::Url::from_file_path("/dir/two.rs").unwrap(),
4534 vec![
4535 lsp::TextEdit::new(
4536 lsp::Range::new(
4537 lsp::Position::new(0, 24),
4538 lsp::Position::new(0, 27),
4539 ),
4540 "THREE".to_string(),
4541 ),
4542 lsp::TextEdit::new(
4543 lsp::Range::new(
4544 lsp::Position::new(0, 35),
4545 lsp::Position::new(0, 38),
4546 ),
4547 "THREE".to_string(),
4548 ),
4549 ],
4550 ),
4551 ]
4552 .into_iter()
4553 .collect(),
4554 ),
4555 ..Default::default()
4556 }))
4557 })
4558 .next()
4559 .await
4560 .unwrap();
4561 confirm_rename.await.unwrap();
4562
4563 let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
4564 workspace
4565 .active_item(cx)
4566 .unwrap()
4567 .downcast::<Editor>()
4568 .unwrap()
4569 });
4570 rename_editor.update(cx_b, |editor, cx| {
4571 assert_eq!(
4572 editor.text(cx),
4573 "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
4574 );
4575 editor.undo(&Undo, cx);
4576 assert_eq!(
4577 editor.text(cx),
4578 "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
4579 );
4580 editor.redo(&Redo, cx);
4581 assert_eq!(
4582 editor.text(cx),
4583 "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
4584 );
4585 });
4586
4587 // Ensure temporary rename edits cannot be undone/redone.
4588 editor_b.update(cx_b, |editor, cx| {
4589 editor.undo(&Undo, cx);
4590 assert_eq!(editor.text(cx), "const ONE: usize = 1;");
4591 editor.undo(&Undo, cx);
4592 assert_eq!(editor.text(cx), "const ONE: usize = 1;");
4593 editor.redo(&Redo, cx);
4594 assert_eq!(editor.text(cx), "const THREE: usize = 1;");
4595 })
4596}
4597
4598#[gpui::test(iterations = 10)]
4599async fn test_language_server_statuses(
4600 deterministic: Arc<Deterministic>,
4601 cx_a: &mut TestAppContext,
4602 cx_b: &mut TestAppContext,
4603) {
4604 deterministic.forbid_parking();
4605
4606 cx_b.update(editor::init);
4607 let mut server = TestServer::start(&deterministic).await;
4608 let client_a = server.create_client(cx_a, "user_a").await;
4609 let client_b = server.create_client(cx_b, "user_b").await;
4610 server
4611 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4612 .await;
4613 let active_call_a = cx_a.read(ActiveCall::global);
4614
4615 // Set up a fake language server.
4616 let mut language = Language::new(
4617 LanguageConfig {
4618 name: "Rust".into(),
4619 path_suffixes: vec!["rs".to_string()],
4620 ..Default::default()
4621 },
4622 Some(tree_sitter_rust::language()),
4623 );
4624 let mut fake_language_servers = language
4625 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4626 name: "the-language-server",
4627 ..Default::default()
4628 }))
4629 .await;
4630 client_a.language_registry.add(Arc::new(language));
4631
4632 client_a
4633 .fs
4634 .insert_tree(
4635 "/dir",
4636 json!({
4637 "main.rs": "const ONE: usize = 1;",
4638 }),
4639 )
4640 .await;
4641 let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
4642
4643 let _buffer_a = project_a
4644 .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
4645 .await
4646 .unwrap();
4647
4648 let fake_language_server = fake_language_servers.next().await.unwrap();
4649 fake_language_server.start_progress("the-token").await;
4650 fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
4651 token: lsp::NumberOrString::String("the-token".to_string()),
4652 value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
4653 lsp::WorkDoneProgressReport {
4654 message: Some("the-message".to_string()),
4655 ..Default::default()
4656 },
4657 )),
4658 });
4659 deterministic.run_until_parked();
4660 project_a.read_with(cx_a, |project, _| {
4661 let status = project.language_server_statuses().next().unwrap();
4662 assert_eq!(status.name, "the-language-server");
4663 assert_eq!(status.pending_work.len(), 1);
4664 assert_eq!(
4665 status.pending_work["the-token"].message.as_ref().unwrap(),
4666 "the-message"
4667 );
4668 });
4669
4670 let project_id = active_call_a
4671 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4672 .await
4673 .unwrap();
4674 deterministic.run_until_parked();
4675 let project_b = client_b.build_remote_project(project_id, cx_b).await;
4676 project_b.read_with(cx_b, |project, _| {
4677 let status = project.language_server_statuses().next().unwrap();
4678 assert_eq!(status.name, "the-language-server");
4679 });
4680
4681 fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
4682 token: lsp::NumberOrString::String("the-token".to_string()),
4683 value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
4684 lsp::WorkDoneProgressReport {
4685 message: Some("the-message-2".to_string()),
4686 ..Default::default()
4687 },
4688 )),
4689 });
4690 deterministic.run_until_parked();
4691 project_a.read_with(cx_a, |project, _| {
4692 let status = project.language_server_statuses().next().unwrap();
4693 assert_eq!(status.name, "the-language-server");
4694 assert_eq!(status.pending_work.len(), 1);
4695 assert_eq!(
4696 status.pending_work["the-token"].message.as_ref().unwrap(),
4697 "the-message-2"
4698 );
4699 });
4700 project_b.read_with(cx_b, |project, _| {
4701 let status = project.language_server_statuses().next().unwrap();
4702 assert_eq!(status.name, "the-language-server");
4703 assert_eq!(status.pending_work.len(), 1);
4704 assert_eq!(
4705 status.pending_work["the-token"].message.as_ref().unwrap(),
4706 "the-message-2"
4707 );
4708 });
4709}
4710
4711#[gpui::test(iterations = 10)]
4712async fn test_contacts(
4713 deterministic: Arc<Deterministic>,
4714 cx_a: &mut TestAppContext,
4715 cx_b: &mut TestAppContext,
4716 cx_c: &mut TestAppContext,
4717 cx_d: &mut TestAppContext,
4718) {
4719 deterministic.forbid_parking();
4720 let mut server = TestServer::start(&deterministic).await;
4721 let client_a = server.create_client(cx_a, "user_a").await;
4722 let client_b = server.create_client(cx_b, "user_b").await;
4723 let client_c = server.create_client(cx_c, "user_c").await;
4724 let client_d = server.create_client(cx_d, "user_d").await;
4725 server
4726 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
4727 .await;
4728 let active_call_a = cx_a.read(ActiveCall::global);
4729 let active_call_b = cx_b.read(ActiveCall::global);
4730 let active_call_c = cx_c.read(ActiveCall::global);
4731 let _active_call_d = cx_d.read(ActiveCall::global);
4732
4733 deterministic.run_until_parked();
4734 assert_eq!(
4735 contacts(&client_a, cx_a),
4736 [
4737 ("user_b".to_string(), "online", "free"),
4738 ("user_c".to_string(), "online", "free")
4739 ]
4740 );
4741 assert_eq!(
4742 contacts(&client_b, cx_b),
4743 [
4744 ("user_a".to_string(), "online", "free"),
4745 ("user_c".to_string(), "online", "free")
4746 ]
4747 );
4748 assert_eq!(
4749 contacts(&client_c, cx_c),
4750 [
4751 ("user_a".to_string(), "online", "free"),
4752 ("user_b".to_string(), "online", "free")
4753 ]
4754 );
4755 assert_eq!(contacts(&client_d, cx_d), []);
4756
4757 server.disconnect_client(client_c.peer_id().unwrap());
4758 server.forbid_connections();
4759 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
4760 assert_eq!(
4761 contacts(&client_a, cx_a),
4762 [
4763 ("user_b".to_string(), "online", "free"),
4764 ("user_c".to_string(), "offline", "free")
4765 ]
4766 );
4767 assert_eq!(
4768 contacts(&client_b, cx_b),
4769 [
4770 ("user_a".to_string(), "online", "free"),
4771 ("user_c".to_string(), "offline", "free")
4772 ]
4773 );
4774 assert_eq!(contacts(&client_c, cx_c), []);
4775 assert_eq!(contacts(&client_d, cx_d), []);
4776
4777 server.allow_connections();
4778 client_c
4779 .authenticate_and_connect(false, &cx_c.to_async())
4780 .await
4781 .unwrap();
4782
4783 deterministic.run_until_parked();
4784 assert_eq!(
4785 contacts(&client_a, cx_a),
4786 [
4787 ("user_b".to_string(), "online", "free"),
4788 ("user_c".to_string(), "online", "free")
4789 ]
4790 );
4791 assert_eq!(
4792 contacts(&client_b, cx_b),
4793 [
4794 ("user_a".to_string(), "online", "free"),
4795 ("user_c".to_string(), "online", "free")
4796 ]
4797 );
4798 assert_eq!(
4799 contacts(&client_c, cx_c),
4800 [
4801 ("user_a".to_string(), "online", "free"),
4802 ("user_b".to_string(), "online", "free")
4803 ]
4804 );
4805 assert_eq!(contacts(&client_d, cx_d), []);
4806
4807 active_call_a
4808 .update(cx_a, |call, cx| {
4809 call.invite(client_b.user_id().unwrap(), None, cx)
4810 })
4811 .await
4812 .unwrap();
4813 deterministic.run_until_parked();
4814 assert_eq!(
4815 contacts(&client_a, cx_a),
4816 [
4817 ("user_b".to_string(), "online", "busy"),
4818 ("user_c".to_string(), "online", "free")
4819 ]
4820 );
4821 assert_eq!(
4822 contacts(&client_b, cx_b),
4823 [
4824 ("user_a".to_string(), "online", "busy"),
4825 ("user_c".to_string(), "online", "free")
4826 ]
4827 );
4828 assert_eq!(
4829 contacts(&client_c, cx_c),
4830 [
4831 ("user_a".to_string(), "online", "busy"),
4832 ("user_b".to_string(), "online", "busy")
4833 ]
4834 );
4835 assert_eq!(contacts(&client_d, cx_d), []);
4836
4837 // Client B and client D become contacts while client B is being called.
4838 server
4839 .make_contacts(&mut [(&client_b, cx_b), (&client_d, cx_d)])
4840 .await;
4841 deterministic.run_until_parked();
4842 assert_eq!(
4843 contacts(&client_a, cx_a),
4844 [
4845 ("user_b".to_string(), "online", "busy"),
4846 ("user_c".to_string(), "online", "free")
4847 ]
4848 );
4849 assert_eq!(
4850 contacts(&client_b, cx_b),
4851 [
4852 ("user_a".to_string(), "online", "busy"),
4853 ("user_c".to_string(), "online", "free"),
4854 ("user_d".to_string(), "online", "free"),
4855 ]
4856 );
4857 assert_eq!(
4858 contacts(&client_c, cx_c),
4859 [
4860 ("user_a".to_string(), "online", "busy"),
4861 ("user_b".to_string(), "online", "busy")
4862 ]
4863 );
4864 assert_eq!(
4865 contacts(&client_d, cx_d),
4866 [("user_b".to_string(), "online", "busy")]
4867 );
4868
4869 active_call_b.update(cx_b, |call, _| call.decline_incoming().unwrap());
4870 deterministic.run_until_parked();
4871 assert_eq!(
4872 contacts(&client_a, cx_a),
4873 [
4874 ("user_b".to_string(), "online", "free"),
4875 ("user_c".to_string(), "online", "free")
4876 ]
4877 );
4878 assert_eq!(
4879 contacts(&client_b, cx_b),
4880 [
4881 ("user_a".to_string(), "online", "free"),
4882 ("user_c".to_string(), "online", "free"),
4883 ("user_d".to_string(), "online", "free")
4884 ]
4885 );
4886 assert_eq!(
4887 contacts(&client_c, cx_c),
4888 [
4889 ("user_a".to_string(), "online", "free"),
4890 ("user_b".to_string(), "online", "free")
4891 ]
4892 );
4893 assert_eq!(
4894 contacts(&client_d, cx_d),
4895 [("user_b".to_string(), "online", "free")]
4896 );
4897
4898 active_call_c
4899 .update(cx_c, |call, cx| {
4900 call.invite(client_a.user_id().unwrap(), None, cx)
4901 })
4902 .await
4903 .unwrap();
4904 deterministic.run_until_parked();
4905 assert_eq!(
4906 contacts(&client_a, cx_a),
4907 [
4908 ("user_b".to_string(), "online", "free"),
4909 ("user_c".to_string(), "online", "busy")
4910 ]
4911 );
4912 assert_eq!(
4913 contacts(&client_b, cx_b),
4914 [
4915 ("user_a".to_string(), "online", "busy"),
4916 ("user_c".to_string(), "online", "busy"),
4917 ("user_d".to_string(), "online", "free")
4918 ]
4919 );
4920 assert_eq!(
4921 contacts(&client_c, cx_c),
4922 [
4923 ("user_a".to_string(), "online", "busy"),
4924 ("user_b".to_string(), "online", "free")
4925 ]
4926 );
4927 assert_eq!(
4928 contacts(&client_d, cx_d),
4929 [("user_b".to_string(), "online", "free")]
4930 );
4931
4932 active_call_a
4933 .update(cx_a, |call, cx| call.accept_incoming(cx))
4934 .await
4935 .unwrap();
4936 deterministic.run_until_parked();
4937 assert_eq!(
4938 contacts(&client_a, cx_a),
4939 [
4940 ("user_b".to_string(), "online", "free"),
4941 ("user_c".to_string(), "online", "busy")
4942 ]
4943 );
4944 assert_eq!(
4945 contacts(&client_b, cx_b),
4946 [
4947 ("user_a".to_string(), "online", "busy"),
4948 ("user_c".to_string(), "online", "busy"),
4949 ("user_d".to_string(), "online", "free")
4950 ]
4951 );
4952 assert_eq!(
4953 contacts(&client_c, cx_c),
4954 [
4955 ("user_a".to_string(), "online", "busy"),
4956 ("user_b".to_string(), "online", "free")
4957 ]
4958 );
4959 assert_eq!(
4960 contacts(&client_d, cx_d),
4961 [("user_b".to_string(), "online", "free")]
4962 );
4963
4964 active_call_a
4965 .update(cx_a, |call, cx| {
4966 call.invite(client_b.user_id().unwrap(), None, cx)
4967 })
4968 .await
4969 .unwrap();
4970 deterministic.run_until_parked();
4971 assert_eq!(
4972 contacts(&client_a, cx_a),
4973 [
4974 ("user_b".to_string(), "online", "busy"),
4975 ("user_c".to_string(), "online", "busy")
4976 ]
4977 );
4978 assert_eq!(
4979 contacts(&client_b, cx_b),
4980 [
4981 ("user_a".to_string(), "online", "busy"),
4982 ("user_c".to_string(), "online", "busy"),
4983 ("user_d".to_string(), "online", "free")
4984 ]
4985 );
4986 assert_eq!(
4987 contacts(&client_c, cx_c),
4988 [
4989 ("user_a".to_string(), "online", "busy"),
4990 ("user_b".to_string(), "online", "busy")
4991 ]
4992 );
4993 assert_eq!(
4994 contacts(&client_d, cx_d),
4995 [("user_b".to_string(), "online", "busy")]
4996 );
4997
4998 active_call_a.update(cx_a, |call, cx| call.hang_up(cx).unwrap());
4999 deterministic.run_until_parked();
5000 assert_eq!(
5001 contacts(&client_a, cx_a),
5002 [
5003 ("user_b".to_string(), "online", "free"),
5004 ("user_c".to_string(), "online", "free")
5005 ]
5006 );
5007 assert_eq!(
5008 contacts(&client_b, cx_b),
5009 [
5010 ("user_a".to_string(), "online", "free"),
5011 ("user_c".to_string(), "online", "free"),
5012 ("user_d".to_string(), "online", "free")
5013 ]
5014 );
5015 assert_eq!(
5016 contacts(&client_c, cx_c),
5017 [
5018 ("user_a".to_string(), "online", "free"),
5019 ("user_b".to_string(), "online", "free")
5020 ]
5021 );
5022 assert_eq!(
5023 contacts(&client_d, cx_d),
5024 [("user_b".to_string(), "online", "free")]
5025 );
5026
5027 active_call_a
5028 .update(cx_a, |call, cx| {
5029 call.invite(client_b.user_id().unwrap(), None, cx)
5030 })
5031 .await
5032 .unwrap();
5033 deterministic.run_until_parked();
5034 assert_eq!(
5035 contacts(&client_a, cx_a),
5036 [
5037 ("user_b".to_string(), "online", "busy"),
5038 ("user_c".to_string(), "online", "free")
5039 ]
5040 );
5041 assert_eq!(
5042 contacts(&client_b, cx_b),
5043 [
5044 ("user_a".to_string(), "online", "busy"),
5045 ("user_c".to_string(), "online", "free"),
5046 ("user_d".to_string(), "online", "free")
5047 ]
5048 );
5049 assert_eq!(
5050 contacts(&client_c, cx_c),
5051 [
5052 ("user_a".to_string(), "online", "busy"),
5053 ("user_b".to_string(), "online", "busy")
5054 ]
5055 );
5056 assert_eq!(
5057 contacts(&client_d, cx_d),
5058 [("user_b".to_string(), "online", "busy")]
5059 );
5060
5061 server.forbid_connections();
5062 server.disconnect_client(client_a.peer_id().unwrap());
5063 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
5064 assert_eq!(contacts(&client_a, cx_a), []);
5065 assert_eq!(
5066 contacts(&client_b, cx_b),
5067 [
5068 ("user_a".to_string(), "offline", "free"),
5069 ("user_c".to_string(), "online", "free"),
5070 ("user_d".to_string(), "online", "free")
5071 ]
5072 );
5073 assert_eq!(
5074 contacts(&client_c, cx_c),
5075 [
5076 ("user_a".to_string(), "offline", "free"),
5077 ("user_b".to_string(), "online", "free")
5078 ]
5079 );
5080 assert_eq!(
5081 contacts(&client_d, cx_d),
5082 [("user_b".to_string(), "online", "free")]
5083 );
5084
5085 fn contacts(
5086 client: &TestClient,
5087 cx: &TestAppContext,
5088 ) -> Vec<(String, &'static str, &'static str)> {
5089 client.user_store.read_with(cx, |store, _| {
5090 store
5091 .contacts()
5092 .iter()
5093 .map(|contact| {
5094 (
5095 contact.user.github_login.clone(),
5096 if contact.online { "online" } else { "offline" },
5097 if contact.busy { "busy" } else { "free" },
5098 )
5099 })
5100 .collect()
5101 })
5102 }
5103}
5104
5105#[gpui::test(iterations = 10)]
5106async fn test_contact_requests(
5107 deterministic: Arc<Deterministic>,
5108 cx_a: &mut TestAppContext,
5109 cx_a2: &mut TestAppContext,
5110 cx_b: &mut TestAppContext,
5111 cx_b2: &mut TestAppContext,
5112 cx_c: &mut TestAppContext,
5113 cx_c2: &mut TestAppContext,
5114) {
5115 deterministic.forbid_parking();
5116
5117 // Connect to a server as 3 clients.
5118 let mut server = TestServer::start(&deterministic).await;
5119 let client_a = server.create_client(cx_a, "user_a").await;
5120 let client_a2 = server.create_client(cx_a2, "user_a").await;
5121 let client_b = server.create_client(cx_b, "user_b").await;
5122 let client_b2 = server.create_client(cx_b2, "user_b").await;
5123 let client_c = server.create_client(cx_c, "user_c").await;
5124 let client_c2 = server.create_client(cx_c2, "user_c").await;
5125
5126 assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
5127 assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
5128 assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
5129
5130 // User A and User C request that user B become their contact.
5131 client_a
5132 .user_store
5133 .update(cx_a, |store, cx| {
5134 store.request_contact(client_b.user_id().unwrap(), cx)
5135 })
5136 .await
5137 .unwrap();
5138 client_c
5139 .user_store
5140 .update(cx_c, |store, cx| {
5141 store.request_contact(client_b.user_id().unwrap(), cx)
5142 })
5143 .await
5144 .unwrap();
5145 deterministic.run_until_parked();
5146
5147 // All users see the pending request appear in all their clients.
5148 assert_eq!(
5149 client_a.summarize_contacts(cx_a).outgoing_requests,
5150 &["user_b"]
5151 );
5152 assert_eq!(
5153 client_a2.summarize_contacts(cx_a2).outgoing_requests,
5154 &["user_b"]
5155 );
5156 assert_eq!(
5157 client_b.summarize_contacts(cx_b).incoming_requests,
5158 &["user_a", "user_c"]
5159 );
5160 assert_eq!(
5161 client_b2.summarize_contacts(cx_b2).incoming_requests,
5162 &["user_a", "user_c"]
5163 );
5164 assert_eq!(
5165 client_c.summarize_contacts(cx_c).outgoing_requests,
5166 &["user_b"]
5167 );
5168 assert_eq!(
5169 client_c2.summarize_contacts(cx_c2).outgoing_requests,
5170 &["user_b"]
5171 );
5172
5173 // Contact requests are present upon connecting (tested here via disconnect/reconnect)
5174 disconnect_and_reconnect(&client_a, cx_a).await;
5175 disconnect_and_reconnect(&client_b, cx_b).await;
5176 disconnect_and_reconnect(&client_c, cx_c).await;
5177 deterministic.run_until_parked();
5178 assert_eq!(
5179 client_a.summarize_contacts(cx_a).outgoing_requests,
5180 &["user_b"]
5181 );
5182 assert_eq!(
5183 client_b.summarize_contacts(cx_b).incoming_requests,
5184 &["user_a", "user_c"]
5185 );
5186 assert_eq!(
5187 client_c.summarize_contacts(cx_c).outgoing_requests,
5188 &["user_b"]
5189 );
5190
5191 // User B accepts the request from user A.
5192 client_b
5193 .user_store
5194 .update(cx_b, |store, cx| {
5195 store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
5196 })
5197 .await
5198 .unwrap();
5199
5200 deterministic.run_until_parked();
5201
5202 // User B sees user A as their contact now in all client, and the incoming request from them is removed.
5203 let contacts_b = client_b.summarize_contacts(cx_b);
5204 assert_eq!(contacts_b.current, &["user_a"]);
5205 assert_eq!(contacts_b.incoming_requests, &["user_c"]);
5206 let contacts_b2 = client_b2.summarize_contacts(cx_b2);
5207 assert_eq!(contacts_b2.current, &["user_a"]);
5208 assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
5209
5210 // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
5211 let contacts_a = client_a.summarize_contacts(cx_a);
5212 assert_eq!(contacts_a.current, &["user_b"]);
5213 assert!(contacts_a.outgoing_requests.is_empty());
5214 let contacts_a2 = client_a2.summarize_contacts(cx_a2);
5215 assert_eq!(contacts_a2.current, &["user_b"]);
5216 assert!(contacts_a2.outgoing_requests.is_empty());
5217
5218 // Contacts are present upon connecting (tested here via disconnect/reconnect)
5219 disconnect_and_reconnect(&client_a, cx_a).await;
5220 disconnect_and_reconnect(&client_b, cx_b).await;
5221 disconnect_and_reconnect(&client_c, cx_c).await;
5222 deterministic.run_until_parked();
5223 assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
5224 assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
5225 assert_eq!(
5226 client_b.summarize_contacts(cx_b).incoming_requests,
5227 &["user_c"]
5228 );
5229 assert!(client_c.summarize_contacts(cx_c).current.is_empty());
5230 assert_eq!(
5231 client_c.summarize_contacts(cx_c).outgoing_requests,
5232 &["user_b"]
5233 );
5234
5235 // User B rejects the request from user C.
5236 client_b
5237 .user_store
5238 .update(cx_b, |store, cx| {
5239 store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
5240 })
5241 .await
5242 .unwrap();
5243
5244 deterministic.run_until_parked();
5245
5246 // User B doesn't see user C as their contact, and the incoming request from them is removed.
5247 let contacts_b = client_b.summarize_contacts(cx_b);
5248 assert_eq!(contacts_b.current, &["user_a"]);
5249 assert!(contacts_b.incoming_requests.is_empty());
5250 let contacts_b2 = client_b2.summarize_contacts(cx_b2);
5251 assert_eq!(contacts_b2.current, &["user_a"]);
5252 assert!(contacts_b2.incoming_requests.is_empty());
5253
5254 // User C doesn't see user B as their contact, and the outgoing request to them is removed.
5255 let contacts_c = client_c.summarize_contacts(cx_c);
5256 assert!(contacts_c.current.is_empty());
5257 assert!(contacts_c.outgoing_requests.is_empty());
5258 let contacts_c2 = client_c2.summarize_contacts(cx_c2);
5259 assert!(contacts_c2.current.is_empty());
5260 assert!(contacts_c2.outgoing_requests.is_empty());
5261
5262 // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
5263 disconnect_and_reconnect(&client_a, cx_a).await;
5264 disconnect_and_reconnect(&client_b, cx_b).await;
5265 disconnect_and_reconnect(&client_c, cx_c).await;
5266 deterministic.run_until_parked();
5267 assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
5268 assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
5269 assert!(client_b
5270 .summarize_contacts(cx_b)
5271 .incoming_requests
5272 .is_empty());
5273 assert!(client_c.summarize_contacts(cx_c).current.is_empty());
5274 assert!(client_c
5275 .summarize_contacts(cx_c)
5276 .outgoing_requests
5277 .is_empty());
5278
5279 async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
5280 client.disconnect(&cx.to_async()).unwrap();
5281 client.clear_contacts(cx).await;
5282 client
5283 .authenticate_and_connect(false, &cx.to_async())
5284 .await
5285 .unwrap();
5286 }
5287}
5288
5289#[gpui::test(iterations = 10)]
5290async fn test_following(
5291 deterministic: Arc<Deterministic>,
5292 cx_a: &mut TestAppContext,
5293 cx_b: &mut TestAppContext,
5294) {
5295 deterministic.forbid_parking();
5296 cx_a.update(editor::init);
5297 cx_b.update(editor::init);
5298
5299 let mut server = TestServer::start(&deterministic).await;
5300 let client_a = server.create_client(cx_a, "user_a").await;
5301 let client_b = server.create_client(cx_b, "user_b").await;
5302 server
5303 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5304 .await;
5305 let active_call_a = cx_a.read(ActiveCall::global);
5306 let active_call_b = cx_b.read(ActiveCall::global);
5307
5308 client_a
5309 .fs
5310 .insert_tree(
5311 "/a",
5312 json!({
5313 "1.txt": "one\none\none",
5314 "2.txt": "two\ntwo\ntwo",
5315 "3.txt": "three\nthree\nthree",
5316 }),
5317 )
5318 .await;
5319 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5320 active_call_a
5321 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5322 .await
5323 .unwrap();
5324
5325 let project_id = active_call_a
5326 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5327 .await
5328 .unwrap();
5329 let project_b = client_b.build_remote_project(project_id, cx_b).await;
5330 active_call_b
5331 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5332 .await
5333 .unwrap();
5334
5335 // Client A opens some editors.
5336 let workspace_a = client_a.build_workspace(&project_a, cx_a);
5337 let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
5338 let editor_a1 = workspace_a
5339 .update(cx_a, |workspace, cx| {
5340 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5341 })
5342 .await
5343 .unwrap()
5344 .downcast::<Editor>()
5345 .unwrap();
5346 let editor_a2 = workspace_a
5347 .update(cx_a, |workspace, cx| {
5348 workspace.open_path((worktree_id, "2.txt"), None, true, cx)
5349 })
5350 .await
5351 .unwrap()
5352 .downcast::<Editor>()
5353 .unwrap();
5354
5355 // Client B opens an editor.
5356 let workspace_b = client_b.build_workspace(&project_b, cx_b);
5357 let editor_b1 = workspace_b
5358 .update(cx_b, |workspace, cx| {
5359 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5360 })
5361 .await
5362 .unwrap()
5363 .downcast::<Editor>()
5364 .unwrap();
5365
5366 let client_a_id = project_b.read_with(cx_b, |project, _| {
5367 project.collaborators().values().next().unwrap().peer_id
5368 });
5369 let client_b_id = project_a.read_with(cx_a, |project, _| {
5370 project.collaborators().values().next().unwrap().peer_id
5371 });
5372
5373 // When client B starts following client A, all visible view states are replicated to client B.
5374 editor_a1.update(cx_a, |editor, cx| {
5375 editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
5376 });
5377 editor_a2.update(cx_a, |editor, cx| {
5378 editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
5379 });
5380 workspace_b
5381 .update(cx_b, |workspace, cx| {
5382 workspace
5383 .toggle_follow(&ToggleFollow(client_a_id), cx)
5384 .unwrap()
5385 })
5386 .await
5387 .unwrap();
5388
5389 let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
5390 workspace
5391 .active_item(cx)
5392 .unwrap()
5393 .downcast::<Editor>()
5394 .unwrap()
5395 });
5396 assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
5397 assert_eq!(
5398 editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
5399 Some((worktree_id, "2.txt").into())
5400 );
5401 assert_eq!(
5402 editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
5403 vec![2..3]
5404 );
5405 assert_eq!(
5406 editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
5407 vec![0..1]
5408 );
5409
5410 // When client A activates a different editor, client B does so as well.
5411 workspace_a.update(cx_a, |workspace, cx| {
5412 workspace.activate_item(&editor_a1, cx)
5413 });
5414 deterministic.run_until_parked();
5415 workspace_b.read_with(cx_b, |workspace, cx| {
5416 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
5417 });
5418
5419 // When client A opens a multibuffer, client B does so as well.
5420 let multibuffer_a = cx_a.add_model(|cx| {
5421 let buffer_a1 = project_a.update(cx, |project, cx| {
5422 project
5423 .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
5424 .unwrap()
5425 });
5426 let buffer_a2 = project_a.update(cx, |project, cx| {
5427 project
5428 .get_open_buffer(&(worktree_id, "2.txt").into(), cx)
5429 .unwrap()
5430 });
5431 let mut result = MultiBuffer::new(0);
5432 result.push_excerpts(
5433 buffer_a1,
5434 [ExcerptRange {
5435 context: 0..3,
5436 primary: None,
5437 }],
5438 cx,
5439 );
5440 result.push_excerpts(
5441 buffer_a2,
5442 [ExcerptRange {
5443 context: 4..7,
5444 primary: None,
5445 }],
5446 cx,
5447 );
5448 result
5449 });
5450 let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
5451 let editor =
5452 cx.add_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
5453 workspace.add_item(Box::new(editor.clone()), cx);
5454 editor
5455 });
5456 deterministic.run_until_parked();
5457 let multibuffer_editor_b = workspace_b.read_with(cx_b, |workspace, cx| {
5458 workspace
5459 .active_item(cx)
5460 .unwrap()
5461 .downcast::<Editor>()
5462 .unwrap()
5463 });
5464 assert_eq!(
5465 multibuffer_editor_a.read_with(cx_a, |editor, cx| editor.text(cx)),
5466 multibuffer_editor_b.read_with(cx_b, |editor, cx| editor.text(cx)),
5467 );
5468
5469 // When client A navigates back and forth, client B does so as well.
5470 workspace_a
5471 .update(cx_a, |workspace, cx| {
5472 workspace::Pane::go_back(workspace, None, cx)
5473 })
5474 .await;
5475 deterministic.run_until_parked();
5476 workspace_b.read_with(cx_b, |workspace, cx| {
5477 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
5478 });
5479
5480 workspace_a
5481 .update(cx_a, |workspace, cx| {
5482 workspace::Pane::go_back(workspace, None, cx)
5483 })
5484 .await;
5485 deterministic.run_until_parked();
5486 workspace_b.read_with(cx_b, |workspace, cx| {
5487 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
5488 });
5489
5490 workspace_a
5491 .update(cx_a, |workspace, cx| {
5492 workspace::Pane::go_forward(workspace, None, cx)
5493 })
5494 .await;
5495 deterministic.run_until_parked();
5496 workspace_b.read_with(cx_b, |workspace, cx| {
5497 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
5498 });
5499
5500 // Changes to client A's editor are reflected on client B.
5501 editor_a1.update(cx_a, |editor, cx| {
5502 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
5503 });
5504 deterministic.run_until_parked();
5505 editor_b1.read_with(cx_b, |editor, cx| {
5506 assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
5507 });
5508
5509 editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
5510 deterministic.run_until_parked();
5511 editor_b1.read_with(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
5512
5513 editor_a1.update(cx_a, |editor, cx| {
5514 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
5515 editor.set_scroll_position(vec2f(0., 100.), cx);
5516 });
5517 deterministic.run_until_parked();
5518 editor_b1.read_with(cx_b, |editor, cx| {
5519 assert_eq!(editor.selections.ranges(cx), &[3..3]);
5520 });
5521
5522 // After unfollowing, client B stops receiving updates from client A.
5523 workspace_b.update(cx_b, |workspace, cx| {
5524 workspace.unfollow(&workspace.active_pane().clone(), cx)
5525 });
5526 workspace_a.update(cx_a, |workspace, cx| {
5527 workspace.activate_item(&editor_a2, cx)
5528 });
5529 deterministic.run_until_parked();
5530 assert_eq!(
5531 workspace_b.read_with(cx_b, |workspace, cx| workspace
5532 .active_item(cx)
5533 .unwrap()
5534 .id()),
5535 editor_b1.id()
5536 );
5537
5538 // Client A starts following client B.
5539 workspace_a
5540 .update(cx_a, |workspace, cx| {
5541 workspace
5542 .toggle_follow(&ToggleFollow(client_b_id), cx)
5543 .unwrap()
5544 })
5545 .await
5546 .unwrap();
5547 assert_eq!(
5548 workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
5549 Some(client_b_id)
5550 );
5551 assert_eq!(
5552 workspace_a.read_with(cx_a, |workspace, cx| workspace
5553 .active_item(cx)
5554 .unwrap()
5555 .id()),
5556 editor_a1.id()
5557 );
5558
5559 // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
5560 let display = MacOSDisplay::new();
5561 active_call_b
5562 .update(cx_b, |call, cx| call.set_location(None, cx))
5563 .await
5564 .unwrap();
5565 active_call_b
5566 .update(cx_b, |call, cx| {
5567 call.room().unwrap().update(cx, |room, cx| {
5568 room.set_display_sources(vec![display.clone()]);
5569 room.share_screen(cx)
5570 })
5571 })
5572 .await
5573 .unwrap();
5574 deterministic.run_until_parked();
5575 let shared_screen = workspace_a.read_with(cx_a, |workspace, cx| {
5576 workspace
5577 .active_item(cx)
5578 .unwrap()
5579 .downcast::<SharedScreen>()
5580 .unwrap()
5581 });
5582
5583 // Client B activates Zed again, which causes the previous editor to become focused again.
5584 active_call_b
5585 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5586 .await
5587 .unwrap();
5588 deterministic.run_until_parked();
5589 workspace_a.read_with(cx_a, |workspace, cx| {
5590 assert_eq!(workspace.active_item(cx).unwrap().id(), editor_a1.id())
5591 });
5592
5593 // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
5594 workspace_b.update(cx_b, |workspace, cx| {
5595 workspace.activate_item(&multibuffer_editor_b, cx)
5596 });
5597 deterministic.run_until_parked();
5598 workspace_a.read_with(cx_a, |workspace, cx| {
5599 assert_eq!(
5600 workspace.active_item(cx).unwrap().id(),
5601 multibuffer_editor_a.id()
5602 )
5603 });
5604
5605 // Client B activates an external window again, and the previously-opened screen-sharing item
5606 // gets activated.
5607 active_call_b
5608 .update(cx_b, |call, cx| call.set_location(None, cx))
5609 .await
5610 .unwrap();
5611 deterministic.run_until_parked();
5612 assert_eq!(
5613 workspace_a.read_with(cx_a, |workspace, cx| workspace
5614 .active_item(cx)
5615 .unwrap()
5616 .id()),
5617 shared_screen.id()
5618 );
5619
5620 // Following interrupts when client B disconnects.
5621 client_b.disconnect(&cx_b.to_async()).unwrap();
5622 deterministic.run_until_parked();
5623 assert_eq!(
5624 workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
5625 None
5626 );
5627}
5628
5629#[gpui::test]
5630async fn test_following_tab_order(
5631 deterministic: Arc<Deterministic>,
5632 cx_a: &mut TestAppContext,
5633 cx_b: &mut TestAppContext,
5634) {
5635 cx_a.update(editor::init);
5636 cx_b.update(editor::init);
5637
5638 let mut server = TestServer::start(&deterministic).await;
5639 let client_a = server.create_client(cx_a, "user_a").await;
5640 let client_b = server.create_client(cx_b, "user_b").await;
5641 server
5642 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5643 .await;
5644 let active_call_a = cx_a.read(ActiveCall::global);
5645 let active_call_b = cx_b.read(ActiveCall::global);
5646
5647 client_a
5648 .fs
5649 .insert_tree(
5650 "/a",
5651 json!({
5652 "1.txt": "one",
5653 "2.txt": "two",
5654 "3.txt": "three",
5655 }),
5656 )
5657 .await;
5658 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5659 active_call_a
5660 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5661 .await
5662 .unwrap();
5663
5664 let project_id = active_call_a
5665 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5666 .await
5667 .unwrap();
5668 let project_b = client_b.build_remote_project(project_id, cx_b).await;
5669 active_call_b
5670 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5671 .await
5672 .unwrap();
5673
5674 let workspace_a = client_a.build_workspace(&project_a, cx_a);
5675 let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
5676
5677 let workspace_b = client_b.build_workspace(&project_b, cx_b);
5678 let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
5679
5680 let client_b_id = project_a.read_with(cx_a, |project, _| {
5681 project.collaborators().values().next().unwrap().peer_id
5682 });
5683
5684 //Open 1, 3 in that order on client A
5685 workspace_a
5686 .update(cx_a, |workspace, cx| {
5687 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5688 })
5689 .await
5690 .unwrap();
5691 workspace_a
5692 .update(cx_a, |workspace, cx| {
5693 workspace.open_path((worktree_id, "3.txt"), None, true, cx)
5694 })
5695 .await
5696 .unwrap();
5697
5698 let pane_paths = |pane: &ViewHandle<workspace::Pane>, cx: &mut TestAppContext| {
5699 pane.update(cx, |pane, cx| {
5700 pane.items()
5701 .map(|item| {
5702 item.project_path(cx)
5703 .unwrap()
5704 .path
5705 .to_str()
5706 .unwrap()
5707 .to_owned()
5708 })
5709 .collect::<Vec<_>>()
5710 })
5711 };
5712
5713 //Verify that the tabs opened in the order we expect
5714 assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
5715
5716 //Follow client B as client A
5717 workspace_a
5718 .update(cx_a, |workspace, cx| {
5719 workspace
5720 .toggle_follow(&ToggleFollow(client_b_id), cx)
5721 .unwrap()
5722 })
5723 .await
5724 .unwrap();
5725
5726 //Open just 2 on client B
5727 workspace_b
5728 .update(cx_b, |workspace, cx| {
5729 workspace.open_path((worktree_id, "2.txt"), None, true, cx)
5730 })
5731 .await
5732 .unwrap();
5733 deterministic.run_until_parked();
5734
5735 // Verify that newly opened followed file is at the end
5736 assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
5737
5738 //Open just 1 on client B
5739 workspace_b
5740 .update(cx_b, |workspace, cx| {
5741 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5742 })
5743 .await
5744 .unwrap();
5745 assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
5746 deterministic.run_until_parked();
5747
5748 // Verify that following into 1 did not reorder
5749 assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
5750}
5751
5752#[gpui::test(iterations = 10)]
5753async fn test_peers_following_each_other(
5754 deterministic: Arc<Deterministic>,
5755 cx_a: &mut TestAppContext,
5756 cx_b: &mut TestAppContext,
5757) {
5758 deterministic.forbid_parking();
5759 cx_a.update(editor::init);
5760 cx_b.update(editor::init);
5761
5762 let mut server = TestServer::start(&deterministic).await;
5763 let client_a = server.create_client(cx_a, "user_a").await;
5764 let client_b = server.create_client(cx_b, "user_b").await;
5765 server
5766 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5767 .await;
5768 let active_call_a = cx_a.read(ActiveCall::global);
5769 let active_call_b = cx_b.read(ActiveCall::global);
5770
5771 // Client A shares a project.
5772 client_a
5773 .fs
5774 .insert_tree(
5775 "/a",
5776 json!({
5777 "1.txt": "one",
5778 "2.txt": "two",
5779 "3.txt": "three",
5780 "4.txt": "four",
5781 }),
5782 )
5783 .await;
5784 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5785 active_call_a
5786 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5787 .await
5788 .unwrap();
5789 let project_id = active_call_a
5790 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5791 .await
5792 .unwrap();
5793
5794 // Client B joins the project.
5795 let project_b = client_b.build_remote_project(project_id, cx_b).await;
5796 active_call_b
5797 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5798 .await
5799 .unwrap();
5800
5801 // Client A opens some editors.
5802 let workspace_a = client_a.build_workspace(&project_a, cx_a);
5803 let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
5804 let _editor_a1 = workspace_a
5805 .update(cx_a, |workspace, cx| {
5806 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5807 })
5808 .await
5809 .unwrap()
5810 .downcast::<Editor>()
5811 .unwrap();
5812
5813 // Client B opens an editor.
5814 let workspace_b = client_b.build_workspace(&project_b, cx_b);
5815 let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
5816 let _editor_b1 = workspace_b
5817 .update(cx_b, |workspace, cx| {
5818 workspace.open_path((worktree_id, "2.txt"), None, true, cx)
5819 })
5820 .await
5821 .unwrap()
5822 .downcast::<Editor>()
5823 .unwrap();
5824
5825 // Clients A and B follow each other in split panes
5826 workspace_a.update(cx_a, |workspace, cx| {
5827 workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
5828 let pane_a1 = pane_a1.clone();
5829 cx.defer(move |workspace, _| {
5830 assert_ne!(*workspace.active_pane(), pane_a1);
5831 });
5832 });
5833 workspace_a
5834 .update(cx_a, |workspace, cx| {
5835 let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
5836 workspace
5837 .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
5838 .unwrap()
5839 })
5840 .await
5841 .unwrap();
5842 workspace_b.update(cx_b, |workspace, cx| {
5843 workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
5844 let pane_b1 = pane_b1.clone();
5845 cx.defer(move |workspace, _| {
5846 assert_ne!(*workspace.active_pane(), pane_b1);
5847 });
5848 });
5849 workspace_b
5850 .update(cx_b, |workspace, cx| {
5851 let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
5852 workspace
5853 .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
5854 .unwrap()
5855 })
5856 .await
5857 .unwrap();
5858
5859 workspace_a.update(cx_a, |workspace, cx| {
5860 workspace.activate_next_pane(cx);
5861 });
5862 // Wait for focus effects to be fully flushed
5863 workspace_a.update(cx_a, |workspace, _| {
5864 assert_eq!(*workspace.active_pane(), pane_a1);
5865 });
5866
5867 workspace_a
5868 .update(cx_a, |workspace, cx| {
5869 workspace.open_path((worktree_id, "3.txt"), None, true, cx)
5870 })
5871 .await
5872 .unwrap();
5873 workspace_b.update(cx_b, |workspace, cx| {
5874 workspace.activate_next_pane(cx);
5875 });
5876
5877 workspace_b
5878 .update(cx_b, |workspace, cx| {
5879 assert_eq!(*workspace.active_pane(), pane_b1);
5880 workspace.open_path((worktree_id, "4.txt"), None, true, cx)
5881 })
5882 .await
5883 .unwrap();
5884 cx_a.foreground().run_until_parked();
5885
5886 // Ensure leader updates don't change the active pane of followers
5887 workspace_a.read_with(cx_a, |workspace, _| {
5888 assert_eq!(*workspace.active_pane(), pane_a1);
5889 });
5890 workspace_b.read_with(cx_b, |workspace, _| {
5891 assert_eq!(*workspace.active_pane(), pane_b1);
5892 });
5893
5894 // Ensure peers following each other doesn't cause an infinite loop.
5895 assert_eq!(
5896 workspace_a.read_with(cx_a, |workspace, cx| workspace
5897 .active_item(cx)
5898 .unwrap()
5899 .project_path(cx)),
5900 Some((worktree_id, "3.txt").into())
5901 );
5902 workspace_a.update(cx_a, |workspace, cx| {
5903 assert_eq!(
5904 workspace.active_item(cx).unwrap().project_path(cx),
5905 Some((worktree_id, "3.txt").into())
5906 );
5907 workspace.activate_next_pane(cx);
5908 });
5909
5910 workspace_a.update(cx_a, |workspace, cx| {
5911 assert_eq!(
5912 workspace.active_item(cx).unwrap().project_path(cx),
5913 Some((worktree_id, "4.txt").into())
5914 );
5915 });
5916
5917 workspace_b.update(cx_b, |workspace, cx| {
5918 assert_eq!(
5919 workspace.active_item(cx).unwrap().project_path(cx),
5920 Some((worktree_id, "4.txt").into())
5921 );
5922 workspace.activate_next_pane(cx);
5923 });
5924
5925 workspace_b.update(cx_b, |workspace, cx| {
5926 assert_eq!(
5927 workspace.active_item(cx).unwrap().project_path(cx),
5928 Some((worktree_id, "3.txt").into())
5929 );
5930 });
5931}
5932
5933#[gpui::test(iterations = 10)]
5934async fn test_auto_unfollowing(
5935 deterministic: Arc<Deterministic>,
5936 cx_a: &mut TestAppContext,
5937 cx_b: &mut TestAppContext,
5938) {
5939 deterministic.forbid_parking();
5940 cx_a.update(editor::init);
5941 cx_b.update(editor::init);
5942
5943 // 2 clients connect to a server.
5944 let mut server = TestServer::start(&deterministic).await;
5945 let client_a = server.create_client(cx_a, "user_a").await;
5946 let client_b = server.create_client(cx_b, "user_b").await;
5947 server
5948 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5949 .await;
5950 let active_call_a = cx_a.read(ActiveCall::global);
5951 let active_call_b = cx_b.read(ActiveCall::global);
5952
5953 // Client A shares a project.
5954 client_a
5955 .fs
5956 .insert_tree(
5957 "/a",
5958 json!({
5959 "1.txt": "one",
5960 "2.txt": "two",
5961 "3.txt": "three",
5962 }),
5963 )
5964 .await;
5965 let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5966 active_call_a
5967 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
5968 .await
5969 .unwrap();
5970
5971 let project_id = active_call_a
5972 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5973 .await
5974 .unwrap();
5975 let project_b = client_b.build_remote_project(project_id, cx_b).await;
5976 active_call_b
5977 .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
5978 .await
5979 .unwrap();
5980
5981 // Client A opens some editors.
5982 let workspace_a = client_a.build_workspace(&project_a, cx_a);
5983 let _editor_a1 = workspace_a
5984 .update(cx_a, |workspace, cx| {
5985 workspace.open_path((worktree_id, "1.txt"), None, true, cx)
5986 })
5987 .await
5988 .unwrap()
5989 .downcast::<Editor>()
5990 .unwrap();
5991
5992 // Client B starts following client A.
5993 let workspace_b = client_b.build_workspace(&project_b, cx_b);
5994 let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
5995 let leader_id = project_b.read_with(cx_b, |project, _| {
5996 project.collaborators().values().next().unwrap().peer_id
5997 });
5998 workspace_b
5999 .update(cx_b, |workspace, cx| {
6000 workspace
6001 .toggle_follow(&ToggleFollow(leader_id), cx)
6002 .unwrap()
6003 })
6004 .await
6005 .unwrap();
6006 assert_eq!(
6007 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6008 Some(leader_id)
6009 );
6010 let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
6011 workspace
6012 .active_item(cx)
6013 .unwrap()
6014 .downcast::<Editor>()
6015 .unwrap()
6016 });
6017
6018 // When client B moves, it automatically stops following client A.
6019 editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
6020 assert_eq!(
6021 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6022 None
6023 );
6024
6025 workspace_b
6026 .update(cx_b, |workspace, cx| {
6027 workspace
6028 .toggle_follow(&ToggleFollow(leader_id), cx)
6029 .unwrap()
6030 })
6031 .await
6032 .unwrap();
6033 assert_eq!(
6034 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6035 Some(leader_id)
6036 );
6037
6038 // When client B edits, it automatically stops following client A.
6039 editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
6040 assert_eq!(
6041 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6042 None
6043 );
6044
6045 workspace_b
6046 .update(cx_b, |workspace, cx| {
6047 workspace
6048 .toggle_follow(&ToggleFollow(leader_id), cx)
6049 .unwrap()
6050 })
6051 .await
6052 .unwrap();
6053 assert_eq!(
6054 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6055 Some(leader_id)
6056 );
6057
6058 // When client B scrolls, it automatically stops following client A.
6059 editor_b2.update(cx_b, |editor, cx| {
6060 editor.set_scroll_position(vec2f(0., 3.), cx)
6061 });
6062 assert_eq!(
6063 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6064 None
6065 );
6066
6067 workspace_b
6068 .update(cx_b, |workspace, cx| {
6069 workspace
6070 .toggle_follow(&ToggleFollow(leader_id), cx)
6071 .unwrap()
6072 })
6073 .await
6074 .unwrap();
6075 assert_eq!(
6076 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6077 Some(leader_id)
6078 );
6079
6080 // When client B activates a different pane, it continues following client A in the original pane.
6081 workspace_b.update(cx_b, |workspace, cx| {
6082 workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
6083 });
6084 assert_eq!(
6085 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6086 Some(leader_id)
6087 );
6088
6089 workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
6090 assert_eq!(
6091 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6092 Some(leader_id)
6093 );
6094
6095 // When client B activates a different item in the original pane, it automatically stops following client A.
6096 workspace_b
6097 .update(cx_b, |workspace, cx| {
6098 workspace.open_path((worktree_id, "2.txt"), None, true, cx)
6099 })
6100 .await
6101 .unwrap();
6102 assert_eq!(
6103 workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
6104 None
6105 );
6106}
6107
6108#[gpui::test(iterations = 10)]
6109async fn test_peers_simultaneously_following_each_other(
6110 deterministic: Arc<Deterministic>,
6111 cx_a: &mut TestAppContext,
6112 cx_b: &mut TestAppContext,
6113) {
6114 deterministic.forbid_parking();
6115 cx_a.update(editor::init);
6116 cx_b.update(editor::init);
6117
6118 let mut server = TestServer::start(&deterministic).await;
6119 let client_a = server.create_client(cx_a, "user_a").await;
6120 let client_b = server.create_client(cx_b, "user_b").await;
6121 server
6122 .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6123 .await;
6124 let active_call_a = cx_a.read(ActiveCall::global);
6125
6126 client_a.fs.insert_tree("/a", json!({})).await;
6127 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
6128 let workspace_a = client_a.build_workspace(&project_a, cx_a);
6129 let project_id = active_call_a
6130 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6131 .await
6132 .unwrap();
6133
6134 let project_b = client_b.build_remote_project(project_id, cx_b).await;
6135 let workspace_b = client_b.build_workspace(&project_b, cx_b);
6136
6137 deterministic.run_until_parked();
6138 let client_a_id = project_b.read_with(cx_b, |project, _| {
6139 project.collaborators().values().next().unwrap().peer_id
6140 });
6141 let client_b_id = project_a.read_with(cx_a, |project, _| {
6142 project.collaborators().values().next().unwrap().peer_id
6143 });
6144
6145 let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
6146 workspace
6147 .toggle_follow(&ToggleFollow(client_b_id), cx)
6148 .unwrap()
6149 });
6150 let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
6151 workspace
6152 .toggle_follow(&ToggleFollow(client_a_id), cx)
6153 .unwrap()
6154 });
6155
6156 futures::try_join!(a_follow_b, b_follow_a).unwrap();
6157 workspace_a.read_with(cx_a, |workspace, _| {
6158 assert_eq!(
6159 workspace.leader_for_pane(workspace.active_pane()),
6160 Some(client_b_id)
6161 );
6162 });
6163 workspace_b.read_with(cx_b, |workspace, _| {
6164 assert_eq!(
6165 workspace.leader_for_pane(workspace.active_pane()),
6166 Some(client_a_id)
6167 );
6168 });
6169}
6170
6171#[gpui::test(iterations = 100)]
6172async fn test_random_collaboration(
6173 cx: &mut TestAppContext,
6174 deterministic: Arc<Deterministic>,
6175 rng: StdRng,
6176) {
6177 deterministic.forbid_parking();
6178 let rng = Arc::new(Mutex::new(rng));
6179
6180 let max_peers = env::var("MAX_PEERS")
6181 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
6182 .unwrap_or(5);
6183
6184 let max_operations = env::var("OPERATIONS")
6185 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
6186 .unwrap_or(10);
6187
6188 let mut server = TestServer::start(&deterministic).await;
6189 let db = server.app_state.db.clone();
6190
6191 let mut available_guests = Vec::new();
6192 for ix in 0..max_peers {
6193 let username = format!("guest-{}", ix + 1);
6194 let user_id = db
6195 .create_user(
6196 &format!("{username}@example.com"),
6197 false,
6198 NewUserParams {
6199 github_login: username.clone(),
6200 github_user_id: (ix + 1) as i32,
6201 invite_count: 0,
6202 },
6203 )
6204 .await
6205 .unwrap()
6206 .user_id;
6207 available_guests.push((user_id, username));
6208 }
6209
6210 for (ix, (user_id_a, _)) in available_guests.iter().enumerate() {
6211 for (user_id_b, _) in &available_guests[ix + 1..] {
6212 server
6213 .app_state
6214 .db
6215 .send_contact_request(*user_id_a, *user_id_b)
6216 .await
6217 .unwrap();
6218 server
6219 .app_state
6220 .db
6221 .respond_to_contact_request(*user_id_b, *user_id_a, true)
6222 .await
6223 .unwrap();
6224 }
6225 }
6226
6227 let mut clients = Vec::new();
6228 let mut user_ids = Vec::new();
6229 let mut op_start_signals = Vec::new();
6230 let mut next_entity_id = 100000;
6231
6232 let mut operations = 0;
6233 while operations < max_operations {
6234 let distribution = rng.lock().gen_range(0..100);
6235 match distribution {
6236 0..=19 if !available_guests.is_empty() => {
6237 let guest_ix = rng.lock().gen_range(0..available_guests.len());
6238 let (_, guest_username) = available_guests.remove(guest_ix);
6239 log::info!("Adding new connection for {}", guest_username);
6240 next_entity_id += 100000;
6241 let mut guest_cx = TestAppContext::new(
6242 cx.foreground_platform(),
6243 cx.platform(),
6244 deterministic.build_foreground(next_entity_id),
6245 deterministic.build_background(),
6246 cx.font_cache(),
6247 cx.leak_detector(),
6248 next_entity_id,
6249 cx.function_name.clone(),
6250 );
6251
6252 let op_start_signal = futures::channel::mpsc::unbounded();
6253 let guest = server.create_client(&mut guest_cx, &guest_username).await;
6254 user_ids.push(guest.current_user_id(&guest_cx));
6255 op_start_signals.push(op_start_signal.0);
6256 clients.push(guest_cx.foreground().spawn(guest.simulate(
6257 guest_username.clone(),
6258 op_start_signal.1,
6259 rng.clone(),
6260 guest_cx,
6261 )));
6262
6263 log::info!("Added connection for {}", guest_username);
6264 operations += 1;
6265 }
6266 20..=24 if clients.len() > 1 => {
6267 let guest_ix = rng.lock().gen_range(1..clients.len());
6268 log::info!(
6269 "Simulating full disconnection of guest {}",
6270 user_ids[guest_ix]
6271 );
6272 let removed_guest_id = user_ids.remove(guest_ix);
6273 let user_connection_ids = server
6274 .connection_pool
6275 .lock()
6276 .user_connection_ids(removed_guest_id)
6277 .collect::<Vec<_>>();
6278 assert_eq!(user_connection_ids.len(), 1);
6279 let removed_peer_id = user_connection_ids[0].into();
6280 let guest = clients.remove(guest_ix);
6281 op_start_signals.remove(guest_ix);
6282 server.forbid_connections();
6283 server.disconnect_client(removed_peer_id);
6284 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6285 deterministic.start_waiting();
6286 log::info!("Waiting for guest {} to exit...", removed_guest_id);
6287 let (guest, mut guest_cx) = guest.await;
6288 deterministic.finish_waiting();
6289 server.allow_connections();
6290
6291 for project in &guest.remote_projects {
6292 project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
6293 }
6294 for user_id in &user_ids {
6295 let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap();
6296 let pool = server.connection_pool.lock();
6297 for contact in contacts {
6298 if let db::Contact::Accepted { user_id, .. } = contact {
6299 if pool.is_user_online(user_id) {
6300 assert_ne!(
6301 user_id, removed_guest_id,
6302 "removed guest is still a contact of another peer"
6303 );
6304 }
6305 }
6306 }
6307 }
6308
6309 log::info!("{} removed", guest.username);
6310 available_guests.push((removed_guest_id, guest.username.clone()));
6311 guest_cx.update(|cx| {
6312 cx.clear_globals();
6313 drop(guest);
6314 });
6315
6316 operations += 1;
6317 }
6318 25..=29 if clients.len() > 1 => {
6319 let guest_ix = rng.lock().gen_range(1..clients.len());
6320 let user_id = user_ids[guest_ix];
6321 log::info!("Simulating temporary disconnection of guest {}", user_id);
6322 let user_connection_ids = server
6323 .connection_pool
6324 .lock()
6325 .user_connection_ids(user_id)
6326 .collect::<Vec<_>>();
6327 assert_eq!(user_connection_ids.len(), 1);
6328 let peer_id = user_connection_ids[0].into();
6329 server.disconnect_client(peer_id);
6330 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6331 operations += 1;
6332 }
6333 30..=34 => {
6334 log::info!("Simulating server restart");
6335 server.reset().await;
6336 deterministic.advance_clock(RECEIVE_TIMEOUT);
6337 server.start().await.unwrap();
6338 deterministic.advance_clock(CLEANUP_TIMEOUT);
6339 let environment = &server.app_state.config.zed_environment;
6340 let stale_room_ids = server
6341 .app_state
6342 .db
6343 .stale_room_ids(environment, server.id())
6344 .await
6345 .unwrap();
6346 assert_eq!(stale_room_ids, vec![]);
6347 }
6348 _ if !op_start_signals.is_empty() => {
6349 while operations < max_operations && rng.lock().gen_bool(0.7) {
6350 op_start_signals
6351 .choose(&mut *rng.lock())
6352 .unwrap()
6353 .unbounded_send(())
6354 .unwrap();
6355 operations += 1;
6356 }
6357
6358 if rng.lock().gen_bool(0.8) {
6359 deterministic.run_until_parked();
6360 }
6361 }
6362 _ => {}
6363 }
6364 }
6365
6366 drop(op_start_signals);
6367 deterministic.start_waiting();
6368 let clients = futures::future::join_all(clients).await;
6369 deterministic.finish_waiting();
6370 deterministic.run_until_parked();
6371
6372 for (guest_client, guest_cx) in &clients {
6373 for guest_project in &guest_client.remote_projects {
6374 guest_project.read_with(guest_cx, |guest_project, cx| {
6375 let host_project = clients.iter().find_map(|(client, cx)| {
6376 let project = client.local_projects.iter().find(|host_project| {
6377 host_project.read_with(cx, |host_project, _| {
6378 host_project.remote_id() == guest_project.remote_id()
6379 })
6380 })?;
6381 Some((project, cx))
6382 });
6383
6384 if !guest_project.is_read_only() {
6385 if let Some((host_project, host_cx)) = host_project {
6386 let host_worktree_snapshots =
6387 host_project.read_with(host_cx, |host_project, cx| {
6388 host_project
6389 .worktrees(cx)
6390 .map(|worktree| {
6391 let worktree = worktree.read(cx);
6392 (worktree.id(), worktree.snapshot())
6393 })
6394 .collect::<BTreeMap<_, _>>()
6395 });
6396 let guest_worktree_snapshots = guest_project
6397 .worktrees(cx)
6398 .map(|worktree| {
6399 let worktree = worktree.read(cx);
6400 (worktree.id(), worktree.snapshot())
6401 })
6402 .collect::<BTreeMap<_, _>>();
6403
6404 assert_eq!(
6405 guest_worktree_snapshots.keys().collect::<Vec<_>>(),
6406 host_worktree_snapshots.keys().collect::<Vec<_>>(),
6407 "{} has different worktrees than the host",
6408 guest_client.username
6409 );
6410
6411 for (id, host_snapshot) in &host_worktree_snapshots {
6412 let guest_snapshot = &guest_worktree_snapshots[id];
6413 assert_eq!(
6414 guest_snapshot.root_name(),
6415 host_snapshot.root_name(),
6416 "{} has different root name than the host for worktree {}",
6417 guest_client.username,
6418 id
6419 );
6420 assert_eq!(
6421 guest_snapshot.abs_path(),
6422 host_snapshot.abs_path(),
6423 "{} has different abs path than the host for worktree {}",
6424 guest_client.username,
6425 id
6426 );
6427 assert_eq!(
6428 guest_snapshot.entries(false).collect::<Vec<_>>(),
6429 host_snapshot.entries(false).collect::<Vec<_>>(),
6430 "{} has different snapshot than the host for worktree {}",
6431 guest_client.username,
6432 id
6433 );
6434 assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
6435 }
6436 }
6437 }
6438
6439 guest_project.check_invariants(cx);
6440 });
6441 }
6442
6443 for (guest_project, guest_buffers) in &guest_client.buffers {
6444 let project_id = if guest_project.read_with(guest_cx, |project, _| {
6445 project.is_local() || project.is_read_only()
6446 }) {
6447 continue;
6448 } else {
6449 guest_project
6450 .read_with(guest_cx, |project, _| project.remote_id())
6451 .unwrap()
6452 };
6453
6454 let host_project = clients.iter().find_map(|(client, cx)| {
6455 let project = client.local_projects.iter().find(|host_project| {
6456 host_project.read_with(cx, |host_project, _| {
6457 host_project.remote_id() == Some(project_id)
6458 })
6459 })?;
6460 Some((project, cx))
6461 });
6462
6463 let (host_project, host_cx) = if let Some((host_project, host_cx)) = host_project {
6464 (host_project, host_cx)
6465 } else {
6466 continue;
6467 };
6468
6469 for guest_buffer in guest_buffers {
6470 let buffer_id = guest_buffer.read_with(guest_cx, |buffer, _| buffer.remote_id());
6471 let host_buffer = host_project.read_with(host_cx, |project, cx| {
6472 project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
6473 panic!(
6474 "host does not have buffer for guest:{}, peer:{:?}, id:{}",
6475 guest_client.username,
6476 guest_client.peer_id(),
6477 buffer_id
6478 )
6479 })
6480 });
6481 let path = host_buffer
6482 .read_with(host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
6483
6484 assert_eq!(
6485 guest_buffer.read_with(guest_cx, |buffer, _| buffer.deferred_ops_len()),
6486 0,
6487 "{}, buffer {}, path {:?} has deferred operations",
6488 guest_client.username,
6489 buffer_id,
6490 path,
6491 );
6492 assert_eq!(
6493 guest_buffer.read_with(guest_cx, |buffer, _| buffer.text()),
6494 host_buffer.read_with(host_cx, |buffer, _| buffer.text()),
6495 "{}, buffer {}, path {:?}, differs from the host's buffer",
6496 guest_client.username,
6497 buffer_id,
6498 path
6499 );
6500 }
6501 }
6502 }
6503
6504 for (client, mut cx) in clients {
6505 cx.update(|cx| {
6506 cx.clear_globals();
6507 drop(client);
6508 });
6509 }
6510}
6511
6512struct TestServer {
6513 app_state: Arc<AppState>,
6514 server: Arc<Server>,
6515 connection_killers: Arc<Mutex<HashMap<PeerId, Arc<AtomicBool>>>>,
6516 forbid_connections: Arc<AtomicBool>,
6517 _test_db: TestDb,
6518 test_live_kit_server: Arc<live_kit_client::TestServer>,
6519}
6520
6521impl TestServer {
6522 async fn start(deterministic: &Arc<Deterministic>) -> Self {
6523 static NEXT_LIVE_KIT_SERVER_ID: AtomicUsize = AtomicUsize::new(0);
6524
6525 let use_postgres = env::var("USE_POSTGRES").ok();
6526 let use_postgres = use_postgres.as_deref();
6527 let test_db = if use_postgres == Some("true") || use_postgres == Some("1") {
6528 TestDb::postgres(deterministic.build_background())
6529 } else {
6530 TestDb::sqlite(deterministic.build_background())
6531 };
6532 let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst);
6533 let live_kit_server = live_kit_client::TestServer::create(
6534 format!("http://livekit.{}.test", live_kit_server_id),
6535 format!("devkey-{}", live_kit_server_id),
6536 format!("secret-{}", live_kit_server_id),
6537 deterministic.build_background(),
6538 )
6539 .unwrap();
6540 let app_state = Self::build_app_state(&test_db, &live_kit_server).await;
6541 let epoch = app_state
6542 .db
6543 .create_server(&app_state.config.zed_environment)
6544 .await
6545 .unwrap();
6546 let server = Server::new(
6547 epoch,
6548 app_state.clone(),
6549 Executor::Deterministic(deterministic.build_background()),
6550 );
6551 server.start().await.unwrap();
6552 // Advance clock to ensure the server's cleanup task is finished.
6553 deterministic.advance_clock(CLEANUP_TIMEOUT);
6554 Self {
6555 app_state,
6556 server,
6557 connection_killers: Default::default(),
6558 forbid_connections: Default::default(),
6559 _test_db: test_db,
6560 test_live_kit_server: live_kit_server,
6561 }
6562 }
6563
6564 async fn reset(&self) {
6565 self.app_state.db.reset();
6566 let epoch = self
6567 .app_state
6568 .db
6569 .create_server(&self.app_state.config.zed_environment)
6570 .await
6571 .unwrap();
6572 self.server.reset(epoch);
6573 }
6574
6575 async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
6576 cx.update(|cx| {
6577 cx.set_global(HomeDir(Path::new("/tmp/").to_path_buf()));
6578
6579 let mut settings = Settings::test(cx);
6580 settings.projects_online_by_default = false;
6581 cx.set_global(settings);
6582 });
6583
6584 let http = FakeHttpClient::with_404_response();
6585 let user_id = if let Ok(Some(user)) = self
6586 .app_state
6587 .db
6588 .get_user_by_github_account(name, None)
6589 .await
6590 {
6591 user.id
6592 } else {
6593 self.app_state
6594 .db
6595 .create_user(
6596 &format!("{name}@example.com"),
6597 false,
6598 NewUserParams {
6599 github_login: name.into(),
6600 github_user_id: 0,
6601 invite_count: 0,
6602 },
6603 )
6604 .await
6605 .expect("creating user failed")
6606 .user_id
6607 };
6608 let client_name = name.to_string();
6609 let mut client = cx.read(|cx| Client::new(http.clone(), cx));
6610 let server = self.server.clone();
6611 let db = self.app_state.db.clone();
6612 let connection_killers = self.connection_killers.clone();
6613 let forbid_connections = self.forbid_connections.clone();
6614
6615 Arc::get_mut(&mut client)
6616 .unwrap()
6617 .set_id(user_id.0 as usize)
6618 .override_authenticate(move |cx| {
6619 cx.spawn(|_| async move {
6620 let access_token = "the-token".to_string();
6621 Ok(Credentials {
6622 user_id: user_id.0 as u64,
6623 access_token,
6624 })
6625 })
6626 })
6627 .override_establish_connection(move |credentials, cx| {
6628 assert_eq!(credentials.user_id, user_id.0 as u64);
6629 assert_eq!(credentials.access_token, "the-token");
6630
6631 let server = server.clone();
6632 let db = db.clone();
6633 let connection_killers = connection_killers.clone();
6634 let forbid_connections = forbid_connections.clone();
6635 let client_name = client_name.clone();
6636 cx.spawn(move |cx| async move {
6637 if forbid_connections.load(SeqCst) {
6638 Err(EstablishConnectionError::other(anyhow!(
6639 "server is forbidding connections"
6640 )))
6641 } else {
6642 let (client_conn, server_conn, killed) =
6643 Connection::in_memory(cx.background());
6644 let (connection_id_tx, connection_id_rx) = oneshot::channel();
6645 let user = db
6646 .get_user_by_id(user_id)
6647 .await
6648 .expect("retrieving user failed")
6649 .unwrap();
6650 cx.background()
6651 .spawn(server.handle_connection(
6652 server_conn,
6653 client_name,
6654 user,
6655 Some(connection_id_tx),
6656 Executor::Deterministic(cx.background()),
6657 ))
6658 .detach();
6659 let connection_id = connection_id_rx.await.unwrap();
6660 connection_killers
6661 .lock()
6662 .insert(connection_id.into(), killed);
6663 Ok(client_conn)
6664 }
6665 })
6666 });
6667
6668 let fs = FakeFs::new(cx.background());
6669 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
6670 let app_state = Arc::new(workspace::AppState {
6671 client: client.clone(),
6672 user_store: user_store.clone(),
6673 languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
6674 themes: ThemeRegistry::new((), cx.font_cache()),
6675 fs: fs.clone(),
6676 build_window_options: Default::default,
6677 initialize_workspace: |_, _, _| unimplemented!(),
6678 dock_default_item_factory: |_, _| unimplemented!(),
6679 });
6680
6681 Project::init(&client);
6682 cx.update(|cx| {
6683 workspace::init(app_state.clone(), cx);
6684 call::init(client.clone(), user_store.clone(), cx);
6685 });
6686
6687 client
6688 .authenticate_and_connect(false, &cx.to_async())
6689 .await
6690 .unwrap();
6691
6692 let client = TestClient {
6693 client,
6694 username: name.to_string(),
6695 local_projects: Default::default(),
6696 remote_projects: Default::default(),
6697 next_root_dir_id: 0,
6698 user_store,
6699 fs,
6700 language_registry: Arc::new(LanguageRegistry::test()),
6701 buffers: Default::default(),
6702 };
6703 client.wait_for_current_user(cx).await;
6704 client
6705 }
6706
6707 fn disconnect_client(&self, peer_id: PeerId) {
6708 self.connection_killers
6709 .lock()
6710 .remove(&peer_id)
6711 .unwrap()
6712 .store(true, SeqCst);
6713 }
6714
6715 fn forbid_connections(&self) {
6716 self.forbid_connections.store(true, SeqCst);
6717 }
6718
6719 fn allow_connections(&self) {
6720 self.forbid_connections.store(false, SeqCst);
6721 }
6722
6723 async fn make_contacts(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
6724 for ix in 1..clients.len() {
6725 let (left, right) = clients.split_at_mut(ix);
6726 let (client_a, cx_a) = left.last_mut().unwrap();
6727 for (client_b, cx_b) in right {
6728 client_a
6729 .user_store
6730 .update(*cx_a, |store, cx| {
6731 store.request_contact(client_b.user_id().unwrap(), cx)
6732 })
6733 .await
6734 .unwrap();
6735 cx_a.foreground().run_until_parked();
6736 client_b
6737 .user_store
6738 .update(*cx_b, |store, cx| {
6739 store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
6740 })
6741 .await
6742 .unwrap();
6743 }
6744 }
6745 }
6746
6747 async fn create_room(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
6748 self.make_contacts(clients).await;
6749
6750 let (left, right) = clients.split_at_mut(1);
6751 let (_client_a, cx_a) = &mut left[0];
6752 let active_call_a = cx_a.read(ActiveCall::global);
6753
6754 for (client_b, cx_b) in right {
6755 let user_id_b = client_b.current_user_id(*cx_b).to_proto();
6756 active_call_a
6757 .update(*cx_a, |call, cx| call.invite(user_id_b, None, cx))
6758 .await
6759 .unwrap();
6760
6761 cx_b.foreground().run_until_parked();
6762 let active_call_b = cx_b.read(ActiveCall::global);
6763 active_call_b
6764 .update(*cx_b, |call, cx| call.accept_incoming(cx))
6765 .await
6766 .unwrap();
6767 }
6768 }
6769
6770 async fn build_app_state(
6771 test_db: &TestDb,
6772 fake_server: &live_kit_client::TestServer,
6773 ) -> Arc<AppState> {
6774 Arc::new(AppState {
6775 db: test_db.db().clone(),
6776 live_kit_client: Some(Arc::new(fake_server.create_api_client())),
6777 config: Default::default(),
6778 })
6779 }
6780}
6781
6782impl Deref for TestServer {
6783 type Target = Server;
6784
6785 fn deref(&self) -> &Self::Target {
6786 &self.server
6787 }
6788}
6789
6790impl Drop for TestServer {
6791 fn drop(&mut self) {
6792 self.server.teardown();
6793 self.test_live_kit_server.teardown().unwrap();
6794 }
6795}
6796
6797struct TestClient {
6798 client: Arc<Client>,
6799 username: String,
6800 local_projects: Vec<ModelHandle<Project>>,
6801 remote_projects: Vec<ModelHandle<Project>>,
6802 next_root_dir_id: usize,
6803 pub user_store: ModelHandle<UserStore>,
6804 language_registry: Arc<LanguageRegistry>,
6805 fs: Arc<FakeFs>,
6806 buffers: HashMap<ModelHandle<Project>, HashSet<ModelHandle<language::Buffer>>>,
6807}
6808
6809impl Deref for TestClient {
6810 type Target = Arc<Client>;
6811
6812 fn deref(&self) -> &Self::Target {
6813 &self.client
6814 }
6815}
6816
6817struct ContactsSummary {
6818 pub current: Vec<String>,
6819 pub outgoing_requests: Vec<String>,
6820 pub incoming_requests: Vec<String>,
6821}
6822
6823impl TestClient {
6824 pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
6825 UserId::from_proto(
6826 self.user_store
6827 .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
6828 )
6829 }
6830
6831 async fn wait_for_current_user(&self, cx: &TestAppContext) {
6832 let mut authed_user = self
6833 .user_store
6834 .read_with(cx, |user_store, _| user_store.watch_current_user());
6835 while authed_user.next().await.unwrap().is_none() {}
6836 }
6837
6838 async fn clear_contacts(&self, cx: &mut TestAppContext) {
6839 self.user_store
6840 .update(cx, |store, _| store.clear_contacts())
6841 .await;
6842 }
6843
6844 fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
6845 self.user_store.read_with(cx, |store, _| ContactsSummary {
6846 current: store
6847 .contacts()
6848 .iter()
6849 .map(|contact| contact.user.github_login.clone())
6850 .collect(),
6851 outgoing_requests: store
6852 .outgoing_contact_requests()
6853 .iter()
6854 .map(|user| user.github_login.clone())
6855 .collect(),
6856 incoming_requests: store
6857 .incoming_contact_requests()
6858 .iter()
6859 .map(|user| user.github_login.clone())
6860 .collect(),
6861 })
6862 }
6863
6864 async fn build_local_project(
6865 &self,
6866 root_path: impl AsRef<Path>,
6867 cx: &mut TestAppContext,
6868 ) -> (ModelHandle<Project>, WorktreeId) {
6869 let project = cx.update(|cx| {
6870 Project::local(
6871 self.client.clone(),
6872 self.user_store.clone(),
6873 self.language_registry.clone(),
6874 self.fs.clone(),
6875 cx,
6876 )
6877 });
6878 let (worktree, _) = project
6879 .update(cx, |p, cx| {
6880 p.find_or_create_local_worktree(root_path, true, cx)
6881 })
6882 .await
6883 .unwrap();
6884 worktree
6885 .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
6886 .await;
6887 (project, worktree.read_with(cx, |tree, _| tree.id()))
6888 }
6889
6890 async fn build_remote_project(
6891 &self,
6892 host_project_id: u64,
6893 guest_cx: &mut TestAppContext,
6894 ) -> ModelHandle<Project> {
6895 let project_b = guest_cx.spawn(|cx| {
6896 Project::remote(
6897 host_project_id,
6898 self.client.clone(),
6899 self.user_store.clone(),
6900 self.language_registry.clone(),
6901 FakeFs::new(cx.background()),
6902 cx,
6903 )
6904 });
6905 project_b.await.unwrap()
6906 }
6907
6908 fn build_workspace(
6909 &self,
6910 project: &ModelHandle<Project>,
6911 cx: &mut TestAppContext,
6912 ) -> ViewHandle<Workspace> {
6913 let (_, root_view) = cx.add_window(|_| EmptyView);
6914 cx.add_view(&root_view, |cx| {
6915 Workspace::new(
6916 Default::default(),
6917 0,
6918 project.clone(),
6919 |_, _| unimplemented!(),
6920 cx,
6921 )
6922 })
6923 }
6924
6925 pub async fn simulate(
6926 mut self,
6927 username: String,
6928 mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
6929 rng: Arc<Mutex<StdRng>>,
6930 mut cx: TestAppContext,
6931 ) -> (Self, TestAppContext) {
6932 async fn tick(
6933 client: &mut TestClient,
6934 username: &str,
6935 rng: Arc<Mutex<StdRng>>,
6936 cx: &mut TestAppContext,
6937 ) -> anyhow::Result<()> {
6938 let active_call = cx.read(ActiveCall::global);
6939 if active_call.read_with(cx, |call, _| call.incoming().borrow().is_some()) {
6940 if rng.lock().gen() {
6941 log::info!("{}: accepting incoming call", username);
6942 active_call
6943 .update(cx, |call, cx| call.accept_incoming(cx))
6944 .await?;
6945 } else {
6946 log::info!("{}: declining incoming call", username);
6947 active_call.update(cx, |call, _| call.decline_incoming())?;
6948 }
6949 } else {
6950 let available_contacts = client.user_store.read_with(cx, |user_store, _| {
6951 user_store
6952 .contacts()
6953 .iter()
6954 .filter(|contact| contact.online && !contact.busy)
6955 .cloned()
6956 .collect::<Vec<_>>()
6957 });
6958
6959 let distribution = rng.lock().gen_range(0..100);
6960 match distribution {
6961 0..=29 if !available_contacts.is_empty() => {
6962 let contact = available_contacts.choose(&mut *rng.lock()).unwrap();
6963 log::info!("{}: inviting {}", username, contact.user.github_login);
6964 active_call
6965 .update(cx, |call, cx| call.invite(contact.user.id, None, cx))
6966 .await?;
6967 }
6968 30..=39 if active_call.read_with(cx, |call, _| call.room().is_some()) => {
6969 log::info!("{}: hanging up", username);
6970 active_call.update(cx, |call, cx| call.hang_up(cx))?;
6971 }
6972 _ => {}
6973 }
6974 }
6975
6976 let remote_projects =
6977 if let Some(room) = active_call.read_with(cx, |call, _| call.room().cloned()) {
6978 room.read_with(cx, |room, _| {
6979 room.remote_participants()
6980 .values()
6981 .flat_map(|participant| participant.projects.clone())
6982 .collect::<Vec<_>>()
6983 })
6984 } else {
6985 Default::default()
6986 };
6987 let project = if remote_projects.is_empty() || rng.lock().gen() {
6988 if client.local_projects.is_empty() || rng.lock().gen() {
6989 let dir_paths = client.fs.directories().await;
6990 let local_project = if dir_paths.is_empty() || rng.lock().gen() {
6991 let root_path = format!(
6992 "/{}-root-{}",
6993 username,
6994 post_inc(&mut client.next_root_dir_id)
6995 );
6996 let root_path = Path::new(&root_path);
6997 client.fs.create_dir(root_path).await.unwrap();
6998 client
6999 .fs
7000 .create_file(&root_path.join("main.rs"), Default::default())
7001 .await
7002 .unwrap();
7003 log::info!("{}: opening local project at {:?}", username, root_path);
7004 client.build_local_project(root_path, cx).await.0
7005 } else {
7006 let root_path = dir_paths.choose(&mut *rng.lock()).unwrap();
7007 log::info!("{}: opening local project at {:?}", username, root_path);
7008 client.build_local_project(root_path, cx).await.0
7009 };
7010 client.local_projects.push(local_project.clone());
7011 local_project
7012 } else {
7013 client
7014 .local_projects
7015 .choose(&mut *rng.lock())
7016 .unwrap()
7017 .clone()
7018 }
7019 } else {
7020 if client.remote_projects.is_empty() || rng.lock().gen() {
7021 let remote_project_id = remote_projects.choose(&mut *rng.lock()).unwrap().id;
7022 let remote_project = if let Some(project) =
7023 client.remote_projects.iter().find(|project| {
7024 project.read_with(cx, |project, _| {
7025 project.remote_id() == Some(remote_project_id)
7026 })
7027 }) {
7028 project.clone()
7029 } else {
7030 log::info!("{}: opening remote project {}", username, remote_project_id);
7031 let remote_project = Project::remote(
7032 remote_project_id,
7033 client.client.clone(),
7034 client.user_store.clone(),
7035 client.language_registry.clone(),
7036 FakeFs::new(cx.background()),
7037 cx.to_async(),
7038 )
7039 .await?;
7040 client.remote_projects.push(remote_project.clone());
7041 remote_project
7042 };
7043
7044 remote_project
7045 } else {
7046 client
7047 .remote_projects
7048 .choose(&mut *rng.lock())
7049 .unwrap()
7050 .clone()
7051 }
7052 };
7053
7054 if active_call.read_with(cx, |call, _| call.room().is_some()) {
7055 if let Err(error) = active_call
7056 .update(cx, |call, cx| call.share_project(project.clone(), cx))
7057 .await
7058 {
7059 log::error!("{}: error sharing project, {:?}", username, error);
7060 }
7061 }
7062
7063 let buffers = client.buffers.entry(project.clone()).or_default();
7064 let buffer = if buffers.is_empty() || rng.lock().gen() {
7065 let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
7066 project
7067 .worktrees(cx)
7068 .filter(|worktree| {
7069 let worktree = worktree.read(cx);
7070 worktree.is_visible() && worktree.entries(false).any(|e| e.is_file())
7071 })
7072 .choose(&mut *rng.lock())
7073 }) {
7074 worktree
7075 } else {
7076 cx.background().simulate_random_delay().await;
7077 return Ok(());
7078 };
7079
7080 let (worktree_root_name, project_path) = worktree.read_with(cx, |worktree, _| {
7081 let entry = worktree
7082 .entries(false)
7083 .filter(|e| e.is_file())
7084 .choose(&mut *rng.lock())
7085 .unwrap();
7086 (
7087 worktree.root_name().to_string(),
7088 (worktree.id(), entry.path.clone()),
7089 )
7090 });
7091 log::info!(
7092 "{}: opening path {:?} in worktree {} ({})",
7093 username,
7094 project_path.1,
7095 project_path.0,
7096 worktree_root_name,
7097 );
7098 let buffer = project
7099 .update(cx, |project, cx| {
7100 project.open_buffer(project_path.clone(), cx)
7101 })
7102 .await?;
7103 log::info!(
7104 "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
7105 username,
7106 project_path.1,
7107 project_path.0,
7108 worktree_root_name,
7109 buffer.read_with(cx, |buffer, _| buffer.remote_id())
7110 );
7111 buffers.insert(buffer.clone());
7112 buffer
7113 } else {
7114 buffers.iter().choose(&mut *rng.lock()).unwrap().clone()
7115 };
7116
7117 let choice = rng.lock().gen_range(0..100);
7118 match choice {
7119 0..=9 => {
7120 cx.update(|cx| {
7121 log::info!(
7122 "{}: dropping buffer {:?}",
7123 username,
7124 buffer.read(cx).file().unwrap().full_path(cx)
7125 );
7126 buffers.remove(&buffer);
7127 drop(buffer);
7128 });
7129 }
7130 10..=19 => {
7131 let completions = project.update(cx, |project, cx| {
7132 log::info!(
7133 "{}: requesting completions for buffer {} ({:?})",
7134 username,
7135 buffer.read(cx).remote_id(),
7136 buffer.read(cx).file().unwrap().full_path(cx)
7137 );
7138 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
7139 project.completions(&buffer, offset, cx)
7140 });
7141 let completions = cx.background().spawn(async move {
7142 completions
7143 .await
7144 .map_err(|err| anyhow!("completions request failed: {:?}", err))
7145 });
7146 if rng.lock().gen_bool(0.3) {
7147 log::info!("{}: detaching completions request", username);
7148 cx.update(|cx| completions.detach_and_log_err(cx));
7149 } else {
7150 completions.await?;
7151 }
7152 }
7153 20..=29 => {
7154 let code_actions = project.update(cx, |project, cx| {
7155 log::info!(
7156 "{}: requesting code actions for buffer {} ({:?})",
7157 username,
7158 buffer.read(cx).remote_id(),
7159 buffer.read(cx).file().unwrap().full_path(cx)
7160 );
7161 let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
7162 project.code_actions(&buffer, range, cx)
7163 });
7164 let code_actions = cx.background().spawn(async move {
7165 code_actions
7166 .await
7167 .map_err(|err| anyhow!("code actions request failed: {:?}", err))
7168 });
7169 if rng.lock().gen_bool(0.3) {
7170 log::info!("{}: detaching code actions request", username);
7171 cx.update(|cx| code_actions.detach_and_log_err(cx));
7172 } else {
7173 code_actions.await?;
7174 }
7175 }
7176 30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
7177 let (requested_version, save) = buffer.update(cx, |buffer, cx| {
7178 log::info!(
7179 "{}: saving buffer {} ({:?})",
7180 username,
7181 buffer.remote_id(),
7182 buffer.file().unwrap().full_path(cx)
7183 );
7184 (buffer.version(), buffer.save(cx))
7185 });
7186 let save = cx.background().spawn(async move {
7187 let (saved_version, _, _) = save
7188 .await
7189 .map_err(|err| anyhow!("save request failed: {:?}", err))?;
7190 assert!(saved_version.observed_all(&requested_version));
7191 Ok::<_, anyhow::Error>(())
7192 });
7193 if rng.lock().gen_bool(0.3) {
7194 log::info!("{}: detaching save request", username);
7195 cx.update(|cx| save.detach_and_log_err(cx));
7196 } else {
7197 save.await?;
7198 }
7199 }
7200 40..=44 => {
7201 let prepare_rename = project.update(cx, |project, cx| {
7202 log::info!(
7203 "{}: preparing rename for buffer {} ({:?})",
7204 username,
7205 buffer.read(cx).remote_id(),
7206 buffer.read(cx).file().unwrap().full_path(cx)
7207 );
7208 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
7209 project.prepare_rename(buffer, offset, cx)
7210 });
7211 let prepare_rename = cx.background().spawn(async move {
7212 prepare_rename
7213 .await
7214 .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
7215 });
7216 if rng.lock().gen_bool(0.3) {
7217 log::info!("{}: detaching prepare rename request", username);
7218 cx.update(|cx| prepare_rename.detach_and_log_err(cx));
7219 } else {
7220 prepare_rename.await?;
7221 }
7222 }
7223 45..=49 => {
7224 let definitions = project.update(cx, |project, cx| {
7225 log::info!(
7226 "{}: requesting definitions for buffer {} ({:?})",
7227 username,
7228 buffer.read(cx).remote_id(),
7229 buffer.read(cx).file().unwrap().full_path(cx)
7230 );
7231 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
7232 project.definition(&buffer, offset, cx)
7233 });
7234 let definitions = cx.background().spawn(async move {
7235 definitions
7236 .await
7237 .map_err(|err| anyhow!("definitions request failed: {:?}", err))
7238 });
7239 if rng.lock().gen_bool(0.3) {
7240 log::info!("{}: detaching definitions request", username);
7241 cx.update(|cx| definitions.detach_and_log_err(cx));
7242 } else {
7243 buffers.extend(definitions.await?.into_iter().map(|loc| loc.target.buffer));
7244 }
7245 }
7246 50..=54 => {
7247 let highlights = project.update(cx, |project, cx| {
7248 log::info!(
7249 "{}: requesting highlights for buffer {} ({:?})",
7250 username,
7251 buffer.read(cx).remote_id(),
7252 buffer.read(cx).file().unwrap().full_path(cx)
7253 );
7254 let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
7255 project.document_highlights(&buffer, offset, cx)
7256 });
7257 let highlights = cx.background().spawn(async move {
7258 highlights
7259 .await
7260 .map_err(|err| anyhow!("highlights request failed: {:?}", err))
7261 });
7262 if rng.lock().gen_bool(0.3) {
7263 log::info!("{}: detaching highlights request", username);
7264 cx.update(|cx| highlights.detach_and_log_err(cx));
7265 } else {
7266 highlights.await?;
7267 }
7268 }
7269 55..=59 => {
7270 let search = project.update(cx, |project, cx| {
7271 let query = rng.lock().gen_range('a'..='z');
7272 log::info!("{}: project-wide search {:?}", username, query);
7273 project.search(SearchQuery::text(query, false, false), cx)
7274 });
7275 let search = cx.background().spawn(async move {
7276 search
7277 .await
7278 .map_err(|err| anyhow!("search request failed: {:?}", err))
7279 });
7280 if rng.lock().gen_bool(0.3) {
7281 log::info!("{}: detaching search request", username);
7282 cx.update(|cx| search.detach_and_log_err(cx));
7283 } else {
7284 buffers.extend(search.await?.into_keys());
7285 }
7286 }
7287 60..=79 => {
7288 let worktree = project
7289 .read_with(cx, |project, cx| {
7290 project
7291 .worktrees(cx)
7292 .filter(|worktree| {
7293 let worktree = worktree.read(cx);
7294 worktree.is_visible()
7295 && worktree.entries(false).any(|e| e.is_file())
7296 && worktree.root_entry().map_or(false, |e| e.is_dir())
7297 })
7298 .choose(&mut *rng.lock())
7299 })
7300 .unwrap();
7301 let (worktree_id, worktree_root_name) = worktree
7302 .read_with(cx, |worktree, _| {
7303 (worktree.id(), worktree.root_name().to_string())
7304 });
7305
7306 let mut new_name = String::new();
7307 for _ in 0..10 {
7308 let letter = rng.lock().gen_range('a'..='z');
7309 new_name.push(letter);
7310 }
7311
7312 let is_dir = rng.lock().gen::<bool>();
7313 let mut new_path = PathBuf::new();
7314 new_path.push(new_name);
7315 if !is_dir {
7316 new_path.set_extension("rs");
7317 }
7318 log::info!(
7319 "{}: creating {:?} in worktree {} ({})",
7320 username,
7321 new_path,
7322 worktree_id,
7323 worktree_root_name,
7324 );
7325 project
7326 .update(cx, |project, cx| {
7327 project.create_entry((worktree_id, new_path), is_dir, cx)
7328 })
7329 .unwrap()
7330 .await?;
7331 }
7332 _ => {
7333 buffer.update(cx, |buffer, cx| {
7334 log::info!(
7335 "{}: updating buffer {} ({:?})",
7336 username,
7337 buffer.remote_id(),
7338 buffer.file().unwrap().full_path(cx)
7339 );
7340 if rng.lock().gen_bool(0.7) {
7341 buffer.randomly_edit(&mut *rng.lock(), 5, cx);
7342 } else {
7343 buffer.randomly_undo_redo(&mut *rng.lock(), cx);
7344 }
7345 });
7346 }
7347 }
7348
7349 Ok(())
7350 }
7351
7352 // Setup language server
7353 let mut language = Language::new(
7354 LanguageConfig {
7355 name: "Rust".into(),
7356 path_suffixes: vec!["rs".to_string()],
7357 ..Default::default()
7358 },
7359 None,
7360 );
7361 let _fake_language_servers = language
7362 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7363 name: "the-fake-language-server",
7364 capabilities: lsp::LanguageServer::full_capabilities(),
7365 initializer: Some(Box::new({
7366 let rng = rng.clone();
7367 let fs = self.fs.clone();
7368 move |fake_server: &mut FakeLanguageServer| {
7369 fake_server.handle_request::<lsp::request::Completion, _, _>(
7370 |_, _| async move {
7371 Ok(Some(lsp::CompletionResponse::Array(vec![
7372 lsp::CompletionItem {
7373 text_edit: Some(lsp::CompletionTextEdit::Edit(
7374 lsp::TextEdit {
7375 range: lsp::Range::new(
7376 lsp::Position::new(0, 0),
7377 lsp::Position::new(0, 0),
7378 ),
7379 new_text: "the-new-text".to_string(),
7380 },
7381 )),
7382 ..Default::default()
7383 },
7384 ])))
7385 },
7386 );
7387
7388 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
7389 |_, _| async move {
7390 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
7391 lsp::CodeAction {
7392 title: "the-code-action".to_string(),
7393 ..Default::default()
7394 },
7395 )]))
7396 },
7397 );
7398
7399 fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
7400 |params, _| async move {
7401 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
7402 params.position,
7403 params.position,
7404 ))))
7405 },
7406 );
7407
7408 fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
7409 let fs = fs.clone();
7410 let rng = rng.clone();
7411 move |_, _| {
7412 let fs = fs.clone();
7413 let rng = rng.clone();
7414 async move {
7415 let files = fs.files().await;
7416 let mut rng = rng.lock();
7417 let count = rng.gen_range::<usize, _>(1..3);
7418 let files = (0..count)
7419 .map(|_| files.choose(&mut *rng).unwrap())
7420 .collect::<Vec<_>>();
7421 log::info!("LSP: Returning definitions in files {:?}", &files);
7422 Ok(Some(lsp::GotoDefinitionResponse::Array(
7423 files
7424 .into_iter()
7425 .map(|file| lsp::Location {
7426 uri: lsp::Url::from_file_path(file).unwrap(),
7427 range: Default::default(),
7428 })
7429 .collect(),
7430 )))
7431 }
7432 }
7433 });
7434
7435 fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
7436 {
7437 let rng = rng.clone();
7438 move |_, _| {
7439 let mut highlights = Vec::new();
7440 let highlight_count = rng.lock().gen_range(1..=5);
7441 for _ in 0..highlight_count {
7442 let start_row = rng.lock().gen_range(0..100);
7443 let start_column = rng.lock().gen_range(0..100);
7444 let start = PointUtf16::new(start_row, start_column);
7445 let end_row = rng.lock().gen_range(0..100);
7446 let end_column = rng.lock().gen_range(0..100);
7447 let end = PointUtf16::new(end_row, end_column);
7448 let range =
7449 if start > end { end..start } else { start..end };
7450 highlights.push(lsp::DocumentHighlight {
7451 range: range_to_lsp(range.clone()),
7452 kind: Some(lsp::DocumentHighlightKind::READ),
7453 });
7454 }
7455 highlights.sort_unstable_by_key(|highlight| {
7456 (highlight.range.start, highlight.range.end)
7457 });
7458 async move { Ok(Some(highlights)) }
7459 }
7460 },
7461 );
7462 }
7463 })),
7464 ..Default::default()
7465 }))
7466 .await;
7467 self.language_registry.add(Arc::new(language));
7468
7469 while op_start_signal.next().await.is_some() {
7470 if let Err(error) = tick(&mut self, &username, rng.clone(), &mut cx).await {
7471 log::error!("{} error: {:?}", username, error);
7472 }
7473
7474 cx.background().simulate_random_delay().await;
7475 }
7476 log::info!("{}: done", username);
7477
7478 (self, cx)
7479 }
7480}
7481
7482impl Drop for TestClient {
7483 fn drop(&mut self) {
7484 self.client.teardown();
7485 }
7486}
7487
7488#[derive(Debug, Eq, PartialEq)]
7489struct RoomParticipants {
7490 remote: Vec<String>,
7491 pending: Vec<String>,
7492}
7493
7494fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomParticipants {
7495 room.read_with(cx, |room, _| {
7496 let mut remote = room
7497 .remote_participants()
7498 .iter()
7499 .map(|(_, participant)| participant.user.github_login.clone())
7500 .collect::<Vec<_>>();
7501 let mut pending = room
7502 .pending_participants()
7503 .iter()
7504 .map(|user| user.github_login.clone())
7505 .collect::<Vec<_>>();
7506 remote.sort();
7507 pending.sort();
7508 RoomParticipants { remote, pending }
7509 })
7510}