project.rs

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