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