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