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