1pub mod fs;
2mod ignore;
3pub mod worktree;
4
5use anyhow::{anyhow, Result};
6use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
7use clock::ReplicaId;
8use collections::HashMap;
9use futures::Future;
10use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
11use gpui::{
12 AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
13};
14use language::{Buffer, DiagnosticEntry, LanguageRegistry};
15use lsp::DiagnosticSeverity;
16use postage::{prelude::Stream, watch};
17use std::{
18 path::Path,
19 sync::{atomic::AtomicBool, Arc},
20};
21use util::TryFutureExt as _;
22
23pub use fs::*;
24pub use worktree::*;
25
26pub struct Project {
27 worktrees: Vec<ModelHandle<Worktree>>,
28 active_entry: Option<ProjectEntry>,
29 languages: Arc<LanguageRegistry>,
30 client: Arc<client::Client>,
31 user_store: ModelHandle<UserStore>,
32 fs: Arc<dyn Fs>,
33 client_state: ProjectClientState,
34 collaborators: HashMap<PeerId, Collaborator>,
35 subscriptions: Vec<client::Subscription>,
36}
37
38enum ProjectClientState {
39 Local {
40 is_shared: bool,
41 remote_id_tx: watch::Sender<Option<u64>>,
42 remote_id_rx: watch::Receiver<Option<u64>>,
43 _maintain_remote_id_task: Task<Option<()>>,
44 },
45 Remote {
46 sharing_has_stopped: bool,
47 remote_id: u64,
48 replica_id: ReplicaId,
49 },
50}
51
52#[derive(Clone, Debug)]
53pub struct Collaborator {
54 pub user: Arc<User>,
55 pub peer_id: PeerId,
56 pub replica_id: ReplicaId,
57}
58
59#[derive(Debug)]
60pub enum Event {
61 ActiveEntryChanged(Option<ProjectEntry>),
62 WorktreeRemoved(WorktreeId),
63 DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId },
64 DiagnosticsUpdated(ProjectPath),
65}
66
67#[derive(Clone, Debug, Eq, PartialEq, Hash)]
68pub struct ProjectPath {
69 pub worktree_id: WorktreeId,
70 pub path: Arc<Path>,
71}
72
73#[derive(Clone, Debug, Default, PartialEq)]
74pub struct DiagnosticSummary {
75 pub error_count: usize,
76 pub warning_count: usize,
77 pub info_count: usize,
78 pub hint_count: usize,
79}
80
81impl DiagnosticSummary {
82 fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
83 let mut this = Self {
84 error_count: 0,
85 warning_count: 0,
86 info_count: 0,
87 hint_count: 0,
88 };
89
90 for entry in diagnostics {
91 if entry.diagnostic.is_primary {
92 match entry.diagnostic.severity {
93 DiagnosticSeverity::ERROR => this.error_count += 1,
94 DiagnosticSeverity::WARNING => this.warning_count += 1,
95 DiagnosticSeverity::INFORMATION => this.info_count += 1,
96 DiagnosticSeverity::HINT => this.hint_count += 1,
97 _ => {}
98 }
99 }
100 }
101
102 this
103 }
104
105 pub fn to_proto(&self, path: Arc<Path>) -> proto::DiagnosticSummary {
106 proto::DiagnosticSummary {
107 path: path.to_string_lossy().to_string(),
108 error_count: self.error_count as u32,
109 warning_count: self.warning_count as u32,
110 info_count: self.info_count as u32,
111 hint_count: self.hint_count as u32,
112 }
113 }
114}
115
116#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
117pub struct ProjectEntry {
118 pub worktree_id: WorktreeId,
119 pub entry_id: usize,
120}
121
122impl Project {
123 pub fn local(
124 client: Arc<Client>,
125 user_store: ModelHandle<UserStore>,
126 languages: Arc<LanguageRegistry>,
127 fs: Arc<dyn Fs>,
128 cx: &mut MutableAppContext,
129 ) -> ModelHandle<Self> {
130 cx.add_model(|cx: &mut ModelContext<Self>| {
131 let (remote_id_tx, remote_id_rx) = watch::channel();
132 let _maintain_remote_id_task = cx.spawn_weak({
133 let rpc = client.clone();
134 move |this, mut cx| {
135 async move {
136 let mut status = rpc.status();
137 while let Some(status) = status.recv().await {
138 if let Some(this) = this.upgrade(&cx) {
139 let remote_id = if let client::Status::Connected { .. } = status {
140 let response = rpc.request(proto::RegisterProject {}).await?;
141 Some(response.project_id)
142 } else {
143 None
144 };
145
146 if let Some(project_id) = remote_id {
147 let mut registrations = Vec::new();
148 this.read_with(&cx, |this, cx| {
149 for worktree in &this.worktrees {
150 let worktree_id = worktree.id() as u64;
151 let worktree = worktree.read(cx).as_local().unwrap();
152 registrations.push(rpc.request(
153 proto::RegisterWorktree {
154 project_id,
155 worktree_id,
156 root_name: worktree.root_name().to_string(),
157 authorized_logins: worktree.authorized_logins(),
158 },
159 ));
160 }
161 });
162 for registration in registrations {
163 registration.await?;
164 }
165 }
166 this.update(&mut cx, |this, cx| this.set_remote_id(remote_id, cx));
167 }
168 }
169 Ok(())
170 }
171 .log_err()
172 }
173 });
174
175 Self {
176 worktrees: Default::default(),
177 collaborators: Default::default(),
178 client_state: ProjectClientState::Local {
179 is_shared: false,
180 remote_id_tx,
181 remote_id_rx,
182 _maintain_remote_id_task,
183 },
184 subscriptions: Vec::new(),
185 active_entry: None,
186 languages,
187 client,
188 user_store,
189 fs,
190 }
191 })
192 }
193
194 pub async fn remote(
195 remote_id: u64,
196 client: Arc<Client>,
197 user_store: ModelHandle<UserStore>,
198 languages: Arc<LanguageRegistry>,
199 fs: Arc<dyn Fs>,
200 cx: &mut AsyncAppContext,
201 ) -> Result<ModelHandle<Self>> {
202 client.authenticate_and_connect(&cx).await?;
203
204 let response = client
205 .request(proto::JoinProject {
206 project_id: remote_id,
207 })
208 .await?;
209
210 let replica_id = response.replica_id as ReplicaId;
211
212 let mut worktrees = Vec::new();
213 for worktree in response.worktrees {
214 worktrees.push(
215 Worktree::remote(
216 remote_id,
217 replica_id,
218 worktree,
219 client.clone(),
220 user_store.clone(),
221 languages.clone(),
222 cx,
223 )
224 .await?,
225 );
226 }
227
228 let user_ids = response
229 .collaborators
230 .iter()
231 .map(|peer| peer.user_id)
232 .collect();
233 user_store
234 .update(cx, |user_store, cx| user_store.load_users(user_ids, cx))
235 .await?;
236 let mut collaborators = HashMap::default();
237 for message in response.collaborators {
238 let collaborator = Collaborator::from_proto(message, &user_store, cx).await?;
239 collaborators.insert(collaborator.peer_id, collaborator);
240 }
241
242 Ok(cx.add_model(|cx| {
243 let mut this = Self {
244 worktrees: Vec::new(),
245 active_entry: None,
246 collaborators,
247 languages,
248 user_store,
249 fs,
250 subscriptions: vec![
251 client.subscribe_to_entity(remote_id, cx, Self::handle_unshare_project),
252 client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
253 client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
254 client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree),
255 client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree),
256 client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
257 client.subscribe_to_entity(
258 remote_id,
259 cx,
260 Self::handle_update_diagnostic_summary,
261 ),
262 client.subscribe_to_entity(
263 remote_id,
264 cx,
265 Self::handle_disk_based_diagnostics_updated,
266 ),
267 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
268 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
269 ],
270 client,
271 client_state: ProjectClientState::Remote {
272 sharing_has_stopped: false,
273 remote_id,
274 replica_id,
275 },
276 };
277 for worktree in worktrees {
278 this.add_worktree(worktree, cx);
279 }
280 this
281 }))
282 }
283
284 fn set_remote_id(&mut self, remote_id: Option<u64>, cx: &mut ModelContext<Self>) {
285 if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
286 *remote_id_tx.borrow_mut() = remote_id;
287 }
288
289 self.subscriptions.clear();
290 if let Some(remote_id) = remote_id {
291 let client = &self.client;
292 self.subscriptions.extend([
293 client.subscribe_to_entity(remote_id, cx, Self::handle_open_buffer),
294 client.subscribe_to_entity(remote_id, cx, Self::handle_close_buffer),
295 client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
296 client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
297 client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
298 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
299 client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
300 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
301 ]);
302 }
303 }
304
305 pub fn remote_id(&self) -> Option<u64> {
306 match &self.client_state {
307 ProjectClientState::Local { remote_id_rx, .. } => *remote_id_rx.borrow(),
308 ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
309 }
310 }
311
312 pub fn next_remote_id(&self) -> impl Future<Output = u64> {
313 let mut id = None;
314 let mut watch = None;
315 match &self.client_state {
316 ProjectClientState::Local { remote_id_rx, .. } => watch = Some(remote_id_rx.clone()),
317 ProjectClientState::Remote { remote_id, .. } => id = Some(*remote_id),
318 }
319
320 async move {
321 if let Some(id) = id {
322 return id;
323 }
324 let mut watch = watch.unwrap();
325 loop {
326 let id = *watch.borrow();
327 if let Some(id) = id {
328 return id;
329 }
330 watch.recv().await;
331 }
332 }
333 }
334
335 pub fn replica_id(&self) -> ReplicaId {
336 match &self.client_state {
337 ProjectClientState::Local { .. } => 0,
338 ProjectClientState::Remote { replica_id, .. } => *replica_id,
339 }
340 }
341
342 pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
343 &self.collaborators
344 }
345
346 pub fn worktrees(&self) -> &[ModelHandle<Worktree>] {
347 &self.worktrees
348 }
349
350 pub fn worktree_for_id(
351 &self,
352 id: WorktreeId,
353 cx: &AppContext,
354 ) -> Option<ModelHandle<Worktree>> {
355 self.worktrees
356 .iter()
357 .find(|worktree| worktree.read(cx).id() == id)
358 .cloned()
359 }
360
361 pub fn share(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
362 let rpc = self.client.clone();
363 cx.spawn(|this, mut cx| async move {
364 let project_id = this.update(&mut cx, |this, _| {
365 if let ProjectClientState::Local {
366 is_shared,
367 remote_id_rx,
368 ..
369 } = &mut this.client_state
370 {
371 *is_shared = true;
372 remote_id_rx
373 .borrow()
374 .ok_or_else(|| anyhow!("no project id"))
375 } else {
376 Err(anyhow!("can't share a remote project"))
377 }
378 })?;
379
380 rpc.request(proto::ShareProject { project_id }).await?;
381 let mut tasks = Vec::new();
382 this.update(&mut cx, |this, cx| {
383 for worktree in &this.worktrees {
384 worktree.update(cx, |worktree, cx| {
385 let worktree = worktree.as_local_mut().unwrap();
386 tasks.push(worktree.share(project_id, cx));
387 });
388 }
389 });
390 for task in tasks {
391 task.await?;
392 }
393 this.update(&mut cx, |_, cx| cx.notify());
394 Ok(())
395 })
396 }
397
398 pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
399 let rpc = self.client.clone();
400 cx.spawn(|this, mut cx| async move {
401 let project_id = this.update(&mut cx, |this, _| {
402 if let ProjectClientState::Local {
403 is_shared,
404 remote_id_rx,
405 ..
406 } = &mut this.client_state
407 {
408 *is_shared = false;
409 remote_id_rx
410 .borrow()
411 .ok_or_else(|| anyhow!("no project id"))
412 } else {
413 Err(anyhow!("can't share a remote project"))
414 }
415 })?;
416
417 rpc.send(proto::UnshareProject { project_id }).await?;
418 this.update(&mut cx, |this, cx| {
419 this.collaborators.clear();
420 cx.notify()
421 });
422 Ok(())
423 })
424 }
425
426 pub fn is_read_only(&self) -> bool {
427 match &self.client_state {
428 ProjectClientState::Local { .. } => false,
429 ProjectClientState::Remote {
430 sharing_has_stopped,
431 ..
432 } => *sharing_has_stopped,
433 }
434 }
435
436 pub fn is_local(&self) -> bool {
437 match &self.client_state {
438 ProjectClientState::Local { .. } => true,
439 ProjectClientState::Remote { .. } => false,
440 }
441 }
442
443 pub fn open_buffer(
444 &self,
445 path: ProjectPath,
446 cx: &mut ModelContext<Self>,
447 ) -> Task<Result<ModelHandle<Buffer>>> {
448 if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
449 worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx))
450 } else {
451 cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) })
452 }
453 }
454
455 pub fn is_shared(&self) -> bool {
456 match &self.client_state {
457 ProjectClientState::Local { is_shared, .. } => *is_shared,
458 ProjectClientState::Remote { .. } => false,
459 }
460 }
461
462 pub fn add_local_worktree(
463 &mut self,
464 abs_path: impl AsRef<Path>,
465 cx: &mut ModelContext<Self>,
466 ) -> Task<Result<ModelHandle<Worktree>>> {
467 let fs = self.fs.clone();
468 let client = self.client.clone();
469 let user_store = self.user_store.clone();
470 let languages = self.languages.clone();
471 let path = Arc::from(abs_path.as_ref());
472 cx.spawn(|project, mut cx| async move {
473 let worktree =
474 Worktree::open_local(client.clone(), user_store, path, fs, languages, &mut cx)
475 .await?;
476
477 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
478 project.add_worktree(worktree.clone(), cx);
479 (project.remote_id(), project.is_shared())
480 });
481
482 if let Some(project_id) = remote_project_id {
483 let worktree_id = worktree.id() as u64;
484 let register_message = worktree.update(&mut cx, |worktree, _| {
485 let worktree = worktree.as_local_mut().unwrap();
486 proto::RegisterWorktree {
487 project_id,
488 worktree_id,
489 root_name: worktree.root_name().to_string(),
490 authorized_logins: worktree.authorized_logins(),
491 }
492 });
493 client.request(register_message).await?;
494 if is_shared {
495 worktree
496 .update(&mut cx, |worktree, cx| {
497 worktree.as_local_mut().unwrap().share(project_id, cx)
498 })
499 .await?;
500 }
501 }
502
503 Ok(worktree)
504 })
505 }
506
507 fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
508 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
509 cx.subscribe(&worktree, |_, worktree, event, cx| match event {
510 worktree::Event::DiagnosticsUpdated(path) => {
511 cx.emit(Event::DiagnosticsUpdated(ProjectPath {
512 worktree_id: worktree.read(cx).id(),
513 path: path.clone(),
514 }));
515 }
516 worktree::Event::DiskBasedDiagnosticsUpdated => {
517 cx.emit(Event::DiskBasedDiagnosticsUpdated {
518 worktree_id: worktree.read(cx).id(),
519 });
520 }
521 })
522 .detach();
523 self.worktrees.push(worktree);
524 cx.notify();
525 }
526
527 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
528 let new_active_entry = entry.and_then(|project_path| {
529 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
530 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
531 Some(ProjectEntry {
532 worktree_id: project_path.worktree_id,
533 entry_id: entry.id,
534 })
535 });
536 if new_active_entry != self.active_entry {
537 self.active_entry = new_active_entry;
538 cx.emit(Event::ActiveEntryChanged(new_active_entry));
539 }
540 }
541
542 pub fn diagnostic_summaries<'a>(
543 &'a self,
544 cx: &'a AppContext,
545 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
546 self.worktrees.iter().flat_map(move |worktree| {
547 let worktree = worktree.read(cx);
548 let worktree_id = worktree.id();
549 worktree
550 .diagnostic_summaries()
551 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
552 })
553 }
554
555 pub fn active_entry(&self) -> Option<ProjectEntry> {
556 self.active_entry
557 }
558
559 // RPC message handlers
560
561 fn handle_unshare_project(
562 &mut self,
563 _: TypedEnvelope<proto::UnshareProject>,
564 _: Arc<Client>,
565 cx: &mut ModelContext<Self>,
566 ) -> Result<()> {
567 if let ProjectClientState::Remote {
568 sharing_has_stopped,
569 ..
570 } = &mut self.client_state
571 {
572 *sharing_has_stopped = true;
573 self.collaborators.clear();
574 cx.notify();
575 Ok(())
576 } else {
577 unreachable!()
578 }
579 }
580
581 fn handle_add_collaborator(
582 &mut self,
583 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
584 _: Arc<Client>,
585 cx: &mut ModelContext<Self>,
586 ) -> Result<()> {
587 let user_store = self.user_store.clone();
588 let collaborator = envelope
589 .payload
590 .collaborator
591 .take()
592 .ok_or_else(|| anyhow!("empty collaborator"))?;
593
594 cx.spawn(|this, mut cx| {
595 async move {
596 let collaborator =
597 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
598 this.update(&mut cx, |this, cx| {
599 this.collaborators
600 .insert(collaborator.peer_id, collaborator);
601 cx.notify();
602 });
603 Ok(())
604 }
605 .log_err()
606 })
607 .detach();
608
609 Ok(())
610 }
611
612 fn handle_remove_collaborator(
613 &mut self,
614 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
615 _: Arc<Client>,
616 cx: &mut ModelContext<Self>,
617 ) -> Result<()> {
618 let peer_id = PeerId(envelope.payload.peer_id);
619 let replica_id = self
620 .collaborators
621 .remove(&peer_id)
622 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
623 .replica_id;
624 for worktree in &self.worktrees {
625 worktree.update(cx, |worktree, cx| {
626 worktree.remove_collaborator(peer_id, replica_id, cx);
627 })
628 }
629 Ok(())
630 }
631
632 fn handle_share_worktree(
633 &mut self,
634 envelope: TypedEnvelope<proto::ShareWorktree>,
635 client: Arc<Client>,
636 cx: &mut ModelContext<Self>,
637 ) -> Result<()> {
638 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
639 let replica_id = self.replica_id();
640 let worktree = envelope
641 .payload
642 .worktree
643 .ok_or_else(|| anyhow!("invalid worktree"))?;
644 let user_store = self.user_store.clone();
645 let languages = self.languages.clone();
646 cx.spawn(|this, mut cx| {
647 async move {
648 let worktree = Worktree::remote(
649 remote_id, replica_id, worktree, client, user_store, languages, &mut cx,
650 )
651 .await?;
652 this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx));
653 Ok(())
654 }
655 .log_err()
656 })
657 .detach();
658 Ok(())
659 }
660
661 fn handle_unregister_worktree(
662 &mut self,
663 envelope: TypedEnvelope<proto::UnregisterWorktree>,
664 _: Arc<Client>,
665 cx: &mut ModelContext<Self>,
666 ) -> Result<()> {
667 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
668 self.worktrees
669 .retain(|worktree| worktree.read(cx).as_remote().unwrap().id() != worktree_id);
670 cx.notify();
671 Ok(())
672 }
673
674 fn handle_update_worktree(
675 &mut self,
676 envelope: TypedEnvelope<proto::UpdateWorktree>,
677 _: Arc<Client>,
678 cx: &mut ModelContext<Self>,
679 ) -> Result<()> {
680 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
681 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
682 worktree.update(cx, |worktree, cx| {
683 let worktree = worktree.as_remote_mut().unwrap();
684 worktree.update_from_remote(envelope, cx)
685 })?;
686 }
687 Ok(())
688 }
689
690 fn handle_update_diagnostic_summary(
691 &mut self,
692 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
693 _: Arc<Client>,
694 cx: &mut ModelContext<Self>,
695 ) -> Result<()> {
696 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
697 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
698 worktree.update(cx, |worktree, cx| {
699 worktree
700 .as_remote_mut()
701 .unwrap()
702 .update_diagnostic_summary(envelope, cx);
703 });
704 }
705 Ok(())
706 }
707
708 fn handle_disk_based_diagnostics_updated(
709 &mut self,
710 envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
711 _: Arc<Client>,
712 cx: &mut ModelContext<Self>,
713 ) -> Result<()> {
714 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
715 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
716 worktree.update(cx, |worktree, cx| {
717 worktree
718 .as_remote()
719 .unwrap()
720 .disk_based_diagnostics_updated(cx);
721 });
722 }
723 Ok(())
724 }
725
726 pub fn handle_update_buffer(
727 &mut self,
728 envelope: TypedEnvelope<proto::UpdateBuffer>,
729 _: Arc<Client>,
730 cx: &mut ModelContext<Self>,
731 ) -> Result<()> {
732 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
733 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
734 worktree.update(cx, |worktree, cx| {
735 worktree.handle_update_buffer(envelope, cx)
736 })?;
737 }
738 Ok(())
739 }
740
741 pub fn handle_save_buffer(
742 &mut self,
743 envelope: TypedEnvelope<proto::SaveBuffer>,
744 rpc: Arc<Client>,
745 cx: &mut ModelContext<Self>,
746 ) -> Result<()> {
747 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
748 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
749 worktree.update(cx, |worktree, cx| {
750 worktree.handle_save_buffer(envelope, rpc, cx)
751 })?;
752 }
753 Ok(())
754 }
755
756 pub fn handle_open_buffer(
757 &mut self,
758 envelope: TypedEnvelope<proto::OpenBuffer>,
759 rpc: Arc<Client>,
760 cx: &mut ModelContext<Self>,
761 ) -> anyhow::Result<()> {
762 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
763 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
764 return worktree.update(cx, |worktree, cx| {
765 worktree.handle_open_buffer(envelope, rpc, cx)
766 });
767 } else {
768 Err(anyhow!("no such worktree"))
769 }
770 }
771
772 pub fn handle_close_buffer(
773 &mut self,
774 envelope: TypedEnvelope<proto::CloseBuffer>,
775 rpc: Arc<Client>,
776 cx: &mut ModelContext<Self>,
777 ) -> anyhow::Result<()> {
778 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
779 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
780 worktree.update(cx, |worktree, cx| {
781 worktree.handle_close_buffer(envelope, rpc, cx)
782 })?;
783 }
784 Ok(())
785 }
786
787 pub fn handle_buffer_saved(
788 &mut self,
789 envelope: TypedEnvelope<proto::BufferSaved>,
790 _: Arc<Client>,
791 cx: &mut ModelContext<Self>,
792 ) -> Result<()> {
793 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
794 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
795 worktree.update(cx, |worktree, cx| {
796 worktree.handle_buffer_saved(envelope, cx)
797 })?;
798 }
799 Ok(())
800 }
801
802 pub fn match_paths<'a>(
803 &self,
804 query: &'a str,
805 include_ignored: bool,
806 smart_case: bool,
807 max_results: usize,
808 cancel_flag: &'a AtomicBool,
809 cx: &AppContext,
810 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
811 let include_root_name = self.worktrees.len() > 1;
812 let candidate_sets = self
813 .worktrees
814 .iter()
815 .map(|worktree| CandidateSet {
816 snapshot: worktree.read(cx).snapshot(),
817 include_ignored,
818 include_root_name,
819 })
820 .collect::<Vec<_>>();
821
822 let background = cx.background().clone();
823 async move {
824 fuzzy::match_paths(
825 candidate_sets.as_slice(),
826 query,
827 smart_case,
828 max_results,
829 cancel_flag,
830 background,
831 )
832 .await
833 }
834 }
835}
836
837struct CandidateSet {
838 snapshot: Snapshot,
839 include_ignored: bool,
840 include_root_name: bool,
841}
842
843impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
844 type Candidates = CandidateSetIter<'a>;
845
846 fn id(&self) -> usize {
847 self.snapshot.id().to_usize()
848 }
849
850 fn len(&self) -> usize {
851 if self.include_ignored {
852 self.snapshot.file_count()
853 } else {
854 self.snapshot.visible_file_count()
855 }
856 }
857
858 fn prefix(&self) -> Arc<str> {
859 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
860 self.snapshot.root_name().into()
861 } else if self.include_root_name {
862 format!("{}/", self.snapshot.root_name()).into()
863 } else {
864 "".into()
865 }
866 }
867
868 fn candidates(&'a self, start: usize) -> Self::Candidates {
869 CandidateSetIter {
870 traversal: self.snapshot.files(self.include_ignored, start),
871 }
872 }
873}
874
875struct CandidateSetIter<'a> {
876 traversal: Traversal<'a>,
877}
878
879impl<'a> Iterator for CandidateSetIter<'a> {
880 type Item = PathMatchCandidate<'a>;
881
882 fn next(&mut self) -> Option<Self::Item> {
883 self.traversal.next().map(|entry| {
884 if let EntryKind::File(char_bag) = entry.kind {
885 PathMatchCandidate {
886 path: &entry.path,
887 char_bag,
888 }
889 } else {
890 unreachable!()
891 }
892 })
893 }
894}
895
896impl Entity for Project {
897 type Event = Event;
898
899 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
900 match &self.client_state {
901 ProjectClientState::Local { remote_id_rx, .. } => {
902 if let Some(project_id) = *remote_id_rx.borrow() {
903 let rpc = self.client.clone();
904 cx.spawn(|_| async move {
905 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
906 log::error!("error unregistering project: {}", err);
907 }
908 })
909 .detach();
910 }
911 }
912 ProjectClientState::Remote { remote_id, .. } => {
913 let rpc = self.client.clone();
914 let project_id = *remote_id;
915 cx.spawn(|_| async move {
916 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
917 log::error!("error leaving project: {}", err);
918 }
919 })
920 .detach();
921 }
922 }
923 }
924}
925
926impl Collaborator {
927 fn from_proto(
928 message: proto::Collaborator,
929 user_store: &ModelHandle<UserStore>,
930 cx: &mut AsyncAppContext,
931 ) -> impl Future<Output = Result<Self>> {
932 let user = user_store.update(cx, |user_store, cx| {
933 user_store.fetch_user(message.user_id, cx)
934 });
935
936 async move {
937 Ok(Self {
938 peer_id: PeerId(message.peer_id),
939 user: user.await?,
940 replica_id: message.replica_id as ReplicaId,
941 })
942 }
943 }
944}
945
946#[cfg(test)]
947mod tests {
948 use super::*;
949 use client::test::FakeHttpClient;
950 use fs::RealFs;
951 use gpui::TestAppContext;
952 use language::LanguageRegistry;
953 use serde_json::json;
954 use std::{os::unix, path::PathBuf};
955 use util::test::temp_tree;
956
957 #[gpui::test]
958 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
959 let dir = temp_tree(json!({
960 "root": {
961 "apple": "",
962 "banana": {
963 "carrot": {
964 "date": "",
965 "endive": "",
966 }
967 },
968 "fennel": {
969 "grape": "",
970 }
971 }
972 }));
973
974 let root_link_path = dir.path().join("root_link");
975 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
976 unix::fs::symlink(
977 &dir.path().join("root/fennel"),
978 &dir.path().join("root/finnochio"),
979 )
980 .unwrap();
981
982 let project = build_project(&mut cx);
983
984 let tree = project
985 .update(&mut cx, |project, cx| {
986 project.add_local_worktree(&root_link_path, cx)
987 })
988 .await
989 .unwrap();
990
991 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
992 .await;
993 cx.read(|cx| {
994 let tree = tree.read(cx);
995 assert_eq!(tree.file_count(), 5);
996 assert_eq!(
997 tree.inode_for_path("fennel/grape"),
998 tree.inode_for_path("finnochio/grape")
999 );
1000 });
1001
1002 let cancel_flag = Default::default();
1003 let results = project
1004 .read_with(&cx, |project, cx| {
1005 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1006 })
1007 .await;
1008 assert_eq!(
1009 results
1010 .into_iter()
1011 .map(|result| result.path)
1012 .collect::<Vec<Arc<Path>>>(),
1013 vec![
1014 PathBuf::from("banana/carrot/date").into(),
1015 PathBuf::from("banana/carrot/endive").into(),
1016 ]
1017 );
1018 }
1019
1020 #[gpui::test]
1021 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1022 let dir = temp_tree(json!({
1023 "root": {
1024 "dir1": {},
1025 "dir2": {
1026 "dir3": {}
1027 }
1028 }
1029 }));
1030
1031 let project = build_project(&mut cx);
1032 let tree = project
1033 .update(&mut cx, |project, cx| {
1034 project.add_local_worktree(&dir.path(), cx)
1035 })
1036 .await
1037 .unwrap();
1038
1039 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1040 .await;
1041
1042 let cancel_flag = Default::default();
1043 let results = project
1044 .read_with(&cx, |project, cx| {
1045 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1046 })
1047 .await;
1048
1049 assert!(results.is_empty());
1050 }
1051
1052 fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1053 let languages = Arc::new(LanguageRegistry::new());
1054 let fs = Arc::new(RealFs);
1055 let http_client = FakeHttpClient::with_404_response();
1056 let client = client::Client::new(http_client.clone());
1057 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1058 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1059 }
1060}