project.rs

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