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