project.rs

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