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            this.update(&mut cx, |_, cx| cx.notify());
361            Ok(())
362        })
363    }
364
365    pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
366        let rpc = self.client.clone();
367        cx.spawn(|this, mut cx| async move {
368            let project_id = this.update(&mut cx, |this, _| {
369                if let ProjectClientState::Local {
370                    is_shared,
371                    remote_id_rx,
372                    ..
373                } = &mut this.client_state
374                {
375                    *is_shared = false;
376                    remote_id_rx
377                        .borrow()
378                        .ok_or_else(|| anyhow!("no project id"))
379                } else {
380                    Err(anyhow!("can't share a remote project"))
381                }
382            })?;
383
384            rpc.send(proto::UnshareProject { project_id }).await?;
385            this.update(&mut cx, |this, cx| {
386                this.collaborators.clear();
387                cx.notify()
388            });
389            Ok(())
390        })
391    }
392
393    pub fn is_read_only(&self) -> bool {
394        match &self.client_state {
395            ProjectClientState::Local { .. } => false,
396            ProjectClientState::Remote {
397                sharing_has_stopped,
398                ..
399            } => *sharing_has_stopped,
400        }
401    }
402
403    pub fn is_local(&self) -> bool {
404        match &self.client_state {
405            ProjectClientState::Local { .. } => true,
406            ProjectClientState::Remote { .. } => false,
407        }
408    }
409
410    pub fn open_buffer(
411        &self,
412        path: ProjectPath,
413        cx: &mut ModelContext<Self>,
414    ) -> Task<Result<ModelHandle<Buffer>>> {
415        if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
416            worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx))
417        } else {
418            cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) })
419        }
420    }
421
422    pub fn is_shared(&self) -> bool {
423        match &self.client_state {
424            ProjectClientState::Local { is_shared, .. } => *is_shared,
425            ProjectClientState::Remote { .. } => false,
426        }
427    }
428
429    pub fn add_local_worktree(
430        &mut self,
431        abs_path: impl AsRef<Path>,
432        cx: &mut ModelContext<Self>,
433    ) -> Task<Result<ModelHandle<Worktree>>> {
434        let fs = self.fs.clone();
435        let client = self.client.clone();
436        let user_store = self.user_store.clone();
437        let languages = self.languages.clone();
438        let path = Arc::from(abs_path.as_ref());
439        cx.spawn(|project, mut cx| async move {
440            let worktree =
441                Worktree::open_local(client.clone(), user_store, path, fs, languages, &mut cx)
442                    .await?;
443
444            let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
445                project.add_worktree(worktree.clone(), cx);
446                (project.remote_id(), project.is_shared())
447            });
448
449            if let Some(project_id) = remote_project_id {
450                let worktree_id = worktree.id() as u64;
451                let register_message = worktree.update(&mut cx, |worktree, _| {
452                    let worktree = worktree.as_local_mut().unwrap();
453                    proto::RegisterWorktree {
454                        project_id,
455                        worktree_id,
456                        root_name: worktree.root_name().to_string(),
457                        authorized_logins: worktree.authorized_logins(),
458                    }
459                });
460                client.request(register_message).await?;
461                if is_shared {
462                    worktree
463                        .update(&mut cx, |worktree, cx| {
464                            worktree.as_local_mut().unwrap().share(project_id, cx)
465                        })
466                        .await?;
467                }
468            }
469
470            Ok(worktree)
471        })
472    }
473
474    fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
475        cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
476        self.worktrees.push(worktree);
477        cx.notify();
478    }
479
480    pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
481        let new_active_entry = entry.and_then(|project_path| {
482            let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
483            let entry = worktree.read(cx).entry_for_path(project_path.path)?;
484            Some(ProjectEntry {
485                worktree_id: project_path.worktree_id,
486                entry_id: entry.id,
487            })
488        });
489        if new_active_entry != self.active_entry {
490            self.active_entry = new_active_entry;
491            cx.emit(Event::ActiveEntryChanged(new_active_entry));
492        }
493    }
494
495    pub fn diagnostic_summaries<'a>(
496        &'a self,
497        cx: &'a AppContext,
498    ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
499        self.worktrees.iter().flat_map(move |worktree| {
500            let worktree_id = worktree.id();
501            worktree
502                .read(cx)
503                .diagnostic_summaries()
504                .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
505        })
506    }
507
508    pub fn active_entry(&self) -> Option<ProjectEntry> {
509        self.active_entry
510    }
511
512    // RPC message handlers
513
514    fn handle_unshare_project(
515        &mut self,
516        _: TypedEnvelope<proto::UnshareProject>,
517        _: Arc<Client>,
518        cx: &mut ModelContext<Self>,
519    ) -> Result<()> {
520        if let ProjectClientState::Remote {
521            sharing_has_stopped,
522            ..
523        } = &mut self.client_state
524        {
525            *sharing_has_stopped = true;
526            self.collaborators.clear();
527            cx.notify();
528            Ok(())
529        } else {
530            unreachable!()
531        }
532    }
533
534    fn handle_add_collaborator(
535        &mut self,
536        mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
537        _: Arc<Client>,
538        cx: &mut ModelContext<Self>,
539    ) -> Result<()> {
540        let user_store = self.user_store.clone();
541        let collaborator = envelope
542            .payload
543            .collaborator
544            .take()
545            .ok_or_else(|| anyhow!("empty collaborator"))?;
546
547        cx.spawn(|this, mut cx| {
548            async move {
549                let collaborator =
550                    Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
551                this.update(&mut cx, |this, cx| {
552                    this.collaborators
553                        .insert(collaborator.peer_id, collaborator);
554                    cx.notify();
555                });
556                Ok(())
557            }
558            .log_err()
559        })
560        .detach();
561
562        Ok(())
563    }
564
565    fn handle_remove_collaborator(
566        &mut self,
567        envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
568        _: Arc<Client>,
569        cx: &mut ModelContext<Self>,
570    ) -> Result<()> {
571        let peer_id = PeerId(envelope.payload.peer_id);
572        let replica_id = self
573            .collaborators
574            .remove(&peer_id)
575            .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
576            .replica_id;
577        for worktree in &self.worktrees {
578            worktree.update(cx, |worktree, cx| {
579                worktree.remove_collaborator(peer_id, replica_id, cx);
580            })
581        }
582        Ok(())
583    }
584
585    fn handle_share_worktree(
586        &mut self,
587        envelope: TypedEnvelope<proto::ShareWorktree>,
588        client: Arc<Client>,
589        cx: &mut ModelContext<Self>,
590    ) -> Result<()> {
591        let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
592        let replica_id = self.replica_id();
593        let worktree = envelope
594            .payload
595            .worktree
596            .ok_or_else(|| anyhow!("invalid worktree"))?;
597        let user_store = self.user_store.clone();
598        let languages = self.languages.clone();
599        cx.spawn(|this, mut cx| {
600            async move {
601                let worktree = Worktree::remote(
602                    remote_id, replica_id, worktree, client, user_store, languages, &mut cx,
603                )
604                .await?;
605                this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx));
606                Ok(())
607            }
608            .log_err()
609        })
610        .detach();
611        Ok(())
612    }
613
614    fn handle_unregister_worktree(
615        &mut self,
616        envelope: TypedEnvelope<proto::UnregisterWorktree>,
617        _: Arc<Client>,
618        cx: &mut ModelContext<Self>,
619    ) -> Result<()> {
620        self.worktrees.retain(|worktree| {
621            worktree.read(cx).as_remote().unwrap().remote_id() != envelope.payload.worktree_id
622        });
623        cx.notify();
624        Ok(())
625    }
626
627    fn handle_update_worktree(
628        &mut self,
629        envelope: TypedEnvelope<proto::UpdateWorktree>,
630        _: Arc<Client>,
631        cx: &mut ModelContext<Self>,
632    ) -> Result<()> {
633        if let Some(worktree) = self.worktree_for_id(envelope.payload.worktree_id as usize, cx) {
634            worktree.update(cx, |worktree, cx| {
635                let worktree = worktree.as_remote_mut().unwrap();
636                worktree.update_from_remote(envelope, cx)
637            })?;
638        }
639        Ok(())
640    }
641
642    pub fn handle_update_buffer(
643        &mut self,
644        envelope: TypedEnvelope<proto::UpdateBuffer>,
645        _: Arc<Client>,
646        cx: &mut ModelContext<Self>,
647    ) -> Result<()> {
648        if let Some(worktree) = self.worktree_for_id(envelope.payload.worktree_id as usize, cx) {
649            worktree.update(cx, |worktree, cx| {
650                worktree.handle_update_buffer(envelope, cx)
651            })?;
652        }
653        Ok(())
654    }
655
656    pub fn handle_save_buffer(
657        &mut self,
658        envelope: TypedEnvelope<proto::SaveBuffer>,
659        rpc: Arc<Client>,
660        cx: &mut ModelContext<Self>,
661    ) -> Result<()> {
662        if let Some(worktree) = self.worktree_for_id(envelope.payload.worktree_id as usize, cx) {
663            worktree.update(cx, |worktree, cx| {
664                worktree.handle_save_buffer(envelope, rpc, cx)
665            })?;
666        }
667        Ok(())
668    }
669
670    pub fn handle_open_buffer(
671        &mut self,
672        envelope: TypedEnvelope<proto::OpenBuffer>,
673        rpc: Arc<Client>,
674        cx: &mut ModelContext<Self>,
675    ) -> anyhow::Result<()> {
676        if let Some(worktree) = self.worktree_for_id(envelope.payload.worktree_id as usize, cx) {
677            return worktree.update(cx, |worktree, cx| {
678                worktree.handle_open_buffer(envelope, rpc, cx)
679            });
680        } else {
681            Err(anyhow!("no such worktree"))
682        }
683    }
684
685    pub fn handle_close_buffer(
686        &mut self,
687        envelope: TypedEnvelope<proto::CloseBuffer>,
688        rpc: Arc<Client>,
689        cx: &mut ModelContext<Self>,
690    ) -> anyhow::Result<()> {
691        if let Some(worktree) = self.worktree_for_id(envelope.payload.worktree_id as usize, cx) {
692            worktree.update(cx, |worktree, cx| {
693                worktree.handle_close_buffer(envelope, rpc, cx)
694            })?;
695        }
696        Ok(())
697    }
698
699    pub fn handle_buffer_saved(
700        &mut self,
701        envelope: TypedEnvelope<proto::BufferSaved>,
702        _: Arc<Client>,
703        cx: &mut ModelContext<Self>,
704    ) -> Result<()> {
705        if let Some(worktree) = self.worktree_for_id(envelope.payload.worktree_id as usize, cx) {
706            worktree.update(cx, |worktree, cx| {
707                worktree.handle_buffer_saved(envelope, cx)
708            })?;
709        }
710        Ok(())
711    }
712
713    pub fn match_paths<'a>(
714        &self,
715        query: &'a str,
716        include_ignored: bool,
717        smart_case: bool,
718        max_results: usize,
719        cancel_flag: &'a AtomicBool,
720        cx: &AppContext,
721    ) -> impl 'a + Future<Output = Vec<PathMatch>> {
722        let include_root_name = self.worktrees.len() > 1;
723        let candidate_sets = self
724            .worktrees
725            .iter()
726            .map(|worktree| CandidateSet {
727                snapshot: worktree.read(cx).snapshot(),
728                include_ignored,
729                include_root_name,
730            })
731            .collect::<Vec<_>>();
732
733        let background = cx.background().clone();
734        async move {
735            fuzzy::match_paths(
736                candidate_sets.as_slice(),
737                query,
738                smart_case,
739                max_results,
740                cancel_flag,
741                background,
742            )
743            .await
744        }
745    }
746}
747
748struct CandidateSet {
749    snapshot: Snapshot,
750    include_ignored: bool,
751    include_root_name: bool,
752}
753
754impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
755    type Candidates = CandidateSetIter<'a>;
756
757    fn id(&self) -> usize {
758        self.snapshot.id()
759    }
760
761    fn len(&self) -> usize {
762        if self.include_ignored {
763            self.snapshot.file_count()
764        } else {
765            self.snapshot.visible_file_count()
766        }
767    }
768
769    fn prefix(&self) -> Arc<str> {
770        if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
771            self.snapshot.root_name().into()
772        } else if self.include_root_name {
773            format!("{}/", self.snapshot.root_name()).into()
774        } else {
775            "".into()
776        }
777    }
778
779    fn candidates(&'a self, start: usize) -> Self::Candidates {
780        CandidateSetIter {
781            traversal: self.snapshot.files(self.include_ignored, start),
782        }
783    }
784}
785
786struct CandidateSetIter<'a> {
787    traversal: Traversal<'a>,
788}
789
790impl<'a> Iterator for CandidateSetIter<'a> {
791    type Item = PathMatchCandidate<'a>;
792
793    fn next(&mut self) -> Option<Self::Item> {
794        self.traversal.next().map(|entry| {
795            if let EntryKind::File(char_bag) = entry.kind {
796                PathMatchCandidate {
797                    path: &entry.path,
798                    char_bag,
799                }
800            } else {
801                unreachable!()
802            }
803        })
804    }
805}
806
807impl Entity for Project {
808    type Event = Event;
809
810    fn release(&mut self, cx: &mut gpui::MutableAppContext) {
811        match &self.client_state {
812            ProjectClientState::Local { remote_id_rx, .. } => {
813                if let Some(project_id) = *remote_id_rx.borrow() {
814                    let rpc = self.client.clone();
815                    cx.spawn(|_| async move {
816                        if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
817                            log::error!("error unregistering project: {}", err);
818                        }
819                    })
820                    .detach();
821                }
822            }
823            ProjectClientState::Remote { remote_id, .. } => {
824                let rpc = self.client.clone();
825                let project_id = *remote_id;
826                cx.spawn(|_| async move {
827                    if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
828                        log::error!("error leaving project: {}", err);
829                    }
830                })
831                .detach();
832            }
833        }
834    }
835}
836
837impl Collaborator {
838    fn from_proto(
839        message: proto::Collaborator,
840        user_store: &ModelHandle<UserStore>,
841        cx: &mut AsyncAppContext,
842    ) -> impl Future<Output = Result<Self>> {
843        let user = user_store.update(cx, |user_store, cx| {
844            user_store.fetch_user(message.user_id, cx)
845        });
846
847        async move {
848            Ok(Self {
849                peer_id: PeerId(message.peer_id),
850                user: user.await?,
851                replica_id: message.replica_id as ReplicaId,
852            })
853        }
854    }
855}
856
857#[cfg(test)]
858mod tests {
859    use super::*;
860    use client::{http::ServerResponse, test::FakeHttpClient};
861    use fs::RealFs;
862    use gpui::TestAppContext;
863    use language::LanguageRegistry;
864    use serde_json::json;
865    use std::{os::unix, path::PathBuf};
866    use util::test::temp_tree;
867
868    #[gpui::test]
869    async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
870        let dir = temp_tree(json!({
871            "root": {
872                "apple": "",
873                "banana": {
874                    "carrot": {
875                        "date": "",
876                        "endive": "",
877                    }
878                },
879                "fennel": {
880                    "grape": "",
881                }
882            }
883        }));
884
885        let root_link_path = dir.path().join("root_link");
886        unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
887        unix::fs::symlink(
888            &dir.path().join("root/fennel"),
889            &dir.path().join("root/finnochio"),
890        )
891        .unwrap();
892
893        let project = build_project(&mut cx);
894
895        let tree = project
896            .update(&mut cx, |project, cx| {
897                project.add_local_worktree(&root_link_path, cx)
898            })
899            .await
900            .unwrap();
901
902        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
903            .await;
904        cx.read(|cx| {
905            let tree = tree.read(cx);
906            assert_eq!(tree.file_count(), 5);
907            assert_eq!(
908                tree.inode_for_path("fennel/grape"),
909                tree.inode_for_path("finnochio/grape")
910            );
911        });
912
913        let cancel_flag = Default::default();
914        let results = project
915            .read_with(&cx, |project, cx| {
916                project.match_paths("bna", false, false, 10, &cancel_flag, cx)
917            })
918            .await;
919        assert_eq!(
920            results
921                .into_iter()
922                .map(|result| result.path)
923                .collect::<Vec<Arc<Path>>>(),
924            vec![
925                PathBuf::from("banana/carrot/date").into(),
926                PathBuf::from("banana/carrot/endive").into(),
927            ]
928        );
929    }
930
931    #[gpui::test]
932    async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
933        let dir = temp_tree(json!({
934            "root": {
935                "dir1": {},
936                "dir2": {
937                    "dir3": {}
938                }
939            }
940        }));
941
942        let project = build_project(&mut cx);
943        let tree = project
944            .update(&mut cx, |project, cx| {
945                project.add_local_worktree(&dir.path(), cx)
946            })
947            .await
948            .unwrap();
949
950        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
951            .await;
952
953        let cancel_flag = Default::default();
954        let results = project
955            .read_with(&cx, |project, cx| {
956                project.match_paths("dir", false, false, 10, &cancel_flag, cx)
957            })
958            .await;
959
960        assert!(results.is_empty());
961    }
962
963    fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
964        let languages = Arc::new(LanguageRegistry::new());
965        let fs = Arc::new(RealFs);
966        let client = client::Client::new();
967        let http_client = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) });
968        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
969        cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
970    }
971}