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, PathBuf},
  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                for worktree in &this.worktrees {
 432                    worktree.update(cx, |worktree, _| {
 433                        worktree.as_local_mut().unwrap().unshare();
 434                    });
 435                }
 436                cx.notify()
 437            });
 438            Ok(())
 439        })
 440    }
 441
 442    pub fn is_read_only(&self) -> bool {
 443        match &self.client_state {
 444            ProjectClientState::Local { .. } => false,
 445            ProjectClientState::Remote {
 446                sharing_has_stopped,
 447                ..
 448            } => *sharing_has_stopped,
 449        }
 450    }
 451
 452    pub fn is_local(&self) -> bool {
 453        match &self.client_state {
 454            ProjectClientState::Local { .. } => true,
 455            ProjectClientState::Remote { .. } => false,
 456        }
 457    }
 458
 459    pub fn open_buffer(
 460        &self,
 461        path: ProjectPath,
 462        cx: &mut ModelContext<Self>,
 463    ) -> Task<Result<ModelHandle<Buffer>>> {
 464        if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
 465            worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx))
 466        } else {
 467            cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) })
 468        }
 469    }
 470
 471    pub fn save_buffer_as(
 472        &self,
 473        buffer: ModelHandle<Buffer>,
 474        abs_path: &Path,
 475        cx: &mut ModelContext<Project>,
 476    ) -> Task<Result<()>> {
 477        let result = self.worktree_for_abs_path(abs_path, cx);
 478        cx.spawn(|_, mut cx| async move {
 479            let (worktree, path) = result.await?;
 480            worktree
 481                .update(&mut cx, |worktree, cx| {
 482                    worktree
 483                        .as_local()
 484                        .unwrap()
 485                        .save_buffer_as(buffer.clone(), path, cx)
 486                })
 487                .await?;
 488            Ok(())
 489        })
 490    }
 491
 492    pub fn worktree_for_abs_path(
 493        &self,
 494        abs_path: &Path,
 495        cx: &mut ModelContext<Self>,
 496    ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
 497        for tree in &self.worktrees {
 498            if let Some(relative_path) = tree
 499                .read(cx)
 500                .as_local()
 501                .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
 502            {
 503                return Task::ready(Ok((tree.clone(), relative_path.into())));
 504            }
 505        }
 506
 507        let worktree = self.add_local_worktree(abs_path, cx);
 508        cx.background().spawn(async move {
 509            let worktree = worktree.await?;
 510            Ok((worktree, PathBuf::new()))
 511        })
 512    }
 513
 514    pub fn is_shared(&self) -> bool {
 515        match &self.client_state {
 516            ProjectClientState::Local { is_shared, .. } => *is_shared,
 517            ProjectClientState::Remote { .. } => false,
 518        }
 519    }
 520
 521    pub fn add_local_worktree(
 522        &self,
 523        abs_path: impl AsRef<Path>,
 524        cx: &mut ModelContext<Self>,
 525    ) -> Task<Result<ModelHandle<Worktree>>> {
 526        let fs = self.fs.clone();
 527        let client = self.client.clone();
 528        let user_store = self.user_store.clone();
 529        let languages = self.languages.clone();
 530        let path = Arc::from(abs_path.as_ref());
 531        cx.spawn(|project, mut cx| async move {
 532            let worktree =
 533                Worktree::open_local(client.clone(), user_store, path, fs, languages, &mut cx)
 534                    .await?;
 535
 536            let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
 537                project.add_worktree(worktree.clone(), cx);
 538                (project.remote_id(), project.is_shared())
 539            });
 540
 541            if let Some(project_id) = remote_project_id {
 542                let worktree_id = worktree.id() as u64;
 543                let register_message = worktree.update(&mut cx, |worktree, _| {
 544                    let worktree = worktree.as_local_mut().unwrap();
 545                    proto::RegisterWorktree {
 546                        project_id,
 547                        worktree_id,
 548                        root_name: worktree.root_name().to_string(),
 549                        authorized_logins: worktree.authorized_logins(),
 550                    }
 551                });
 552                client.request(register_message).await?;
 553                if is_shared {
 554                    worktree
 555                        .update(&mut cx, |worktree, cx| {
 556                            worktree.as_local_mut().unwrap().share(project_id, cx)
 557                        })
 558                        .await?;
 559                }
 560            }
 561
 562            Ok(worktree)
 563        })
 564    }
 565
 566    fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
 567        cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
 568        cx.subscribe(&worktree, move |this, worktree, event, cx| match event {
 569            worktree::Event::DiagnosticsUpdated(path) => {
 570                cx.emit(Event::DiagnosticsUpdated(ProjectPath {
 571                    worktree_id: worktree.read(cx).id(),
 572                    path: path.clone(),
 573                }));
 574            }
 575            worktree::Event::DiskBasedDiagnosticsUpdating => {
 576                if this.pending_disk_based_diagnostics == 0 {
 577                    cx.emit(Event::DiskBasedDiagnosticsStarted);
 578                }
 579                this.pending_disk_based_diagnostics += 1;
 580            }
 581            worktree::Event::DiskBasedDiagnosticsUpdated => {
 582                this.pending_disk_based_diagnostics -= 1;
 583                cx.emit(Event::DiskBasedDiagnosticsUpdated {
 584                    worktree_id: worktree.read(cx).id(),
 585                });
 586                if this.pending_disk_based_diagnostics == 0 {
 587                    if this.pending_disk_based_diagnostics == 0 {
 588                        cx.emit(Event::DiskBasedDiagnosticsFinished);
 589                    }
 590                }
 591            }
 592        })
 593        .detach();
 594        self.worktrees.push(worktree);
 595        cx.notify();
 596    }
 597
 598    pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
 599        let new_active_entry = entry.and_then(|project_path| {
 600            let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
 601            let entry = worktree.read(cx).entry_for_path(project_path.path)?;
 602            Some(ProjectEntry {
 603                worktree_id: project_path.worktree_id,
 604                entry_id: entry.id,
 605            })
 606        });
 607        if new_active_entry != self.active_entry {
 608            self.active_entry = new_active_entry;
 609            cx.emit(Event::ActiveEntryChanged(new_active_entry));
 610        }
 611    }
 612
 613    pub fn path_for_entry(&self, entry: ProjectEntry, cx: &AppContext) -> Option<ProjectPath> {
 614        let worktree = self.worktree_for_id(entry.worktree_id, cx)?.read(cx);
 615        Some(ProjectPath {
 616            worktree_id: entry.worktree_id,
 617            path: worktree.entry_for_id(entry.entry_id)?.path.clone(),
 618        })
 619    }
 620
 621    pub fn is_running_disk_based_diagnostics(&self) -> bool {
 622        self.pending_disk_based_diagnostics > 0
 623    }
 624
 625    pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
 626        let mut summary = DiagnosticSummary::default();
 627        for (_, path_summary) in self.diagnostic_summaries(cx) {
 628            summary.error_count += path_summary.error_count;
 629            summary.warning_count += path_summary.warning_count;
 630            summary.info_count += path_summary.info_count;
 631            summary.hint_count += path_summary.hint_count;
 632        }
 633        summary
 634    }
 635
 636    pub fn diagnostic_summaries<'a>(
 637        &'a self,
 638        cx: &'a AppContext,
 639    ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
 640        self.worktrees.iter().flat_map(move |worktree| {
 641            let worktree = worktree.read(cx);
 642            let worktree_id = worktree.id();
 643            worktree
 644                .diagnostic_summaries()
 645                .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
 646        })
 647    }
 648
 649    pub fn active_entry(&self) -> Option<ProjectEntry> {
 650        self.active_entry
 651    }
 652
 653    // RPC message handlers
 654
 655    fn handle_unshare_project(
 656        &mut self,
 657        _: TypedEnvelope<proto::UnshareProject>,
 658        _: Arc<Client>,
 659        cx: &mut ModelContext<Self>,
 660    ) -> Result<()> {
 661        if let ProjectClientState::Remote {
 662            sharing_has_stopped,
 663            ..
 664        } = &mut self.client_state
 665        {
 666            *sharing_has_stopped = true;
 667            self.collaborators.clear();
 668            cx.notify();
 669            Ok(())
 670        } else {
 671            unreachable!()
 672        }
 673    }
 674
 675    fn handle_add_collaborator(
 676        &mut self,
 677        mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
 678        _: Arc<Client>,
 679        cx: &mut ModelContext<Self>,
 680    ) -> Result<()> {
 681        let user_store = self.user_store.clone();
 682        let collaborator = envelope
 683            .payload
 684            .collaborator
 685            .take()
 686            .ok_or_else(|| anyhow!("empty collaborator"))?;
 687
 688        cx.spawn(|this, mut cx| {
 689            async move {
 690                let collaborator =
 691                    Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
 692                this.update(&mut cx, |this, cx| {
 693                    this.collaborators
 694                        .insert(collaborator.peer_id, collaborator);
 695                    cx.notify();
 696                });
 697                Ok(())
 698            }
 699            .log_err()
 700        })
 701        .detach();
 702
 703        Ok(())
 704    }
 705
 706    fn handle_remove_collaborator(
 707        &mut self,
 708        envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
 709        _: Arc<Client>,
 710        cx: &mut ModelContext<Self>,
 711    ) -> Result<()> {
 712        let peer_id = PeerId(envelope.payload.peer_id);
 713        let replica_id = self
 714            .collaborators
 715            .remove(&peer_id)
 716            .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
 717            .replica_id;
 718        for worktree in &self.worktrees {
 719            worktree.update(cx, |worktree, cx| {
 720                worktree.remove_collaborator(peer_id, replica_id, cx);
 721            })
 722        }
 723        Ok(())
 724    }
 725
 726    fn handle_share_worktree(
 727        &mut self,
 728        envelope: TypedEnvelope<proto::ShareWorktree>,
 729        client: Arc<Client>,
 730        cx: &mut ModelContext<Self>,
 731    ) -> Result<()> {
 732        let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
 733        let replica_id = self.replica_id();
 734        let worktree = envelope
 735            .payload
 736            .worktree
 737            .ok_or_else(|| anyhow!("invalid worktree"))?;
 738        let user_store = self.user_store.clone();
 739        let languages = self.languages.clone();
 740        cx.spawn(|this, mut cx| {
 741            async move {
 742                let worktree = Worktree::remote(
 743                    remote_id, replica_id, worktree, client, user_store, languages, &mut cx,
 744                )
 745                .await?;
 746                this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx));
 747                Ok(())
 748            }
 749            .log_err()
 750        })
 751        .detach();
 752        Ok(())
 753    }
 754
 755    fn handle_unregister_worktree(
 756        &mut self,
 757        envelope: TypedEnvelope<proto::UnregisterWorktree>,
 758        _: Arc<Client>,
 759        cx: &mut ModelContext<Self>,
 760    ) -> Result<()> {
 761        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 762        self.worktrees
 763            .retain(|worktree| worktree.read(cx).as_remote().unwrap().id() != worktree_id);
 764        cx.notify();
 765        Ok(())
 766    }
 767
 768    fn handle_update_worktree(
 769        &mut self,
 770        envelope: TypedEnvelope<proto::UpdateWorktree>,
 771        _: Arc<Client>,
 772        cx: &mut ModelContext<Self>,
 773    ) -> Result<()> {
 774        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 775        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 776            worktree.update(cx, |worktree, cx| {
 777                let worktree = worktree.as_remote_mut().unwrap();
 778                worktree.update_from_remote(envelope, cx)
 779            })?;
 780        }
 781        Ok(())
 782    }
 783
 784    fn handle_update_diagnostic_summary(
 785        &mut self,
 786        envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
 787        _: Arc<Client>,
 788        cx: &mut ModelContext<Self>,
 789    ) -> Result<()> {
 790        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 791        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 792            worktree.update(cx, |worktree, cx| {
 793                worktree
 794                    .as_remote_mut()
 795                    .unwrap()
 796                    .update_diagnostic_summary(envelope, cx);
 797            });
 798        }
 799        Ok(())
 800    }
 801
 802    fn handle_disk_based_diagnostics_updating(
 803        &mut self,
 804        envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
 805        _: Arc<Client>,
 806        cx: &mut ModelContext<Self>,
 807    ) -> Result<()> {
 808        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 809        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 810            worktree.update(cx, |worktree, cx| {
 811                worktree
 812                    .as_remote()
 813                    .unwrap()
 814                    .disk_based_diagnostics_updating(cx);
 815            });
 816        }
 817        Ok(())
 818    }
 819
 820    fn handle_disk_based_diagnostics_updated(
 821        &mut self,
 822        envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
 823        _: Arc<Client>,
 824        cx: &mut ModelContext<Self>,
 825    ) -> Result<()> {
 826        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 827        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 828            worktree.update(cx, |worktree, cx| {
 829                worktree
 830                    .as_remote()
 831                    .unwrap()
 832                    .disk_based_diagnostics_updated(cx);
 833            });
 834        }
 835        Ok(())
 836    }
 837
 838    pub fn handle_update_buffer(
 839        &mut self,
 840        envelope: TypedEnvelope<proto::UpdateBuffer>,
 841        _: Arc<Client>,
 842        cx: &mut ModelContext<Self>,
 843    ) -> Result<()> {
 844        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 845        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 846            worktree.update(cx, |worktree, cx| {
 847                worktree.handle_update_buffer(envelope, cx)
 848            })?;
 849        }
 850        Ok(())
 851    }
 852
 853    pub fn handle_save_buffer(
 854        &mut self,
 855        envelope: TypedEnvelope<proto::SaveBuffer>,
 856        rpc: Arc<Client>,
 857        cx: &mut ModelContext<Self>,
 858    ) -> Result<()> {
 859        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 860        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 861            worktree.update(cx, |worktree, cx| {
 862                worktree.handle_save_buffer(envelope, rpc, cx)
 863            })?;
 864        }
 865        Ok(())
 866    }
 867
 868    pub fn handle_format_buffer(
 869        &mut self,
 870        envelope: TypedEnvelope<proto::FormatBuffer>,
 871        rpc: Arc<Client>,
 872        cx: &mut ModelContext<Self>,
 873    ) -> Result<()> {
 874        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 875        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 876            worktree.update(cx, |worktree, cx| {
 877                worktree.handle_format_buffer(envelope, rpc, cx)
 878            })?;
 879        }
 880        Ok(())
 881    }
 882
 883    pub fn handle_open_buffer(
 884        &mut self,
 885        envelope: TypedEnvelope<proto::OpenBuffer>,
 886        rpc: Arc<Client>,
 887        cx: &mut ModelContext<Self>,
 888    ) -> anyhow::Result<()> {
 889        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 890        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 891            return worktree.update(cx, |worktree, cx| {
 892                worktree.handle_open_buffer(envelope, rpc, cx)
 893            });
 894        } else {
 895            Err(anyhow!("no such worktree"))
 896        }
 897    }
 898
 899    pub fn handle_close_buffer(
 900        &mut self,
 901        envelope: TypedEnvelope<proto::CloseBuffer>,
 902        rpc: Arc<Client>,
 903        cx: &mut ModelContext<Self>,
 904    ) -> anyhow::Result<()> {
 905        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 906        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 907            worktree.update(cx, |worktree, cx| {
 908                worktree.handle_close_buffer(envelope, rpc, cx)
 909            })?;
 910        }
 911        Ok(())
 912    }
 913
 914    pub fn handle_buffer_saved(
 915        &mut self,
 916        envelope: TypedEnvelope<proto::BufferSaved>,
 917        _: Arc<Client>,
 918        cx: &mut ModelContext<Self>,
 919    ) -> Result<()> {
 920        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 921        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 922            worktree.update(cx, |worktree, cx| {
 923                worktree.handle_buffer_saved(envelope, cx)
 924            })?;
 925        }
 926        Ok(())
 927    }
 928
 929    pub fn match_paths<'a>(
 930        &self,
 931        query: &'a str,
 932        include_ignored: bool,
 933        smart_case: bool,
 934        max_results: usize,
 935        cancel_flag: &'a AtomicBool,
 936        cx: &AppContext,
 937    ) -> impl 'a + Future<Output = Vec<PathMatch>> {
 938        let include_root_name = self.worktrees.len() > 1;
 939        let candidate_sets = self
 940            .worktrees
 941            .iter()
 942            .map(|worktree| CandidateSet {
 943                snapshot: worktree.read(cx).snapshot(),
 944                include_ignored,
 945                include_root_name,
 946            })
 947            .collect::<Vec<_>>();
 948
 949        let background = cx.background().clone();
 950        async move {
 951            fuzzy::match_paths(
 952                candidate_sets.as_slice(),
 953                query,
 954                smart_case,
 955                max_results,
 956                cancel_flag,
 957                background,
 958            )
 959            .await
 960        }
 961    }
 962}
 963
 964struct CandidateSet {
 965    snapshot: Snapshot,
 966    include_ignored: bool,
 967    include_root_name: bool,
 968}
 969
 970impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
 971    type Candidates = CandidateSetIter<'a>;
 972
 973    fn id(&self) -> usize {
 974        self.snapshot.id().to_usize()
 975    }
 976
 977    fn len(&self) -> usize {
 978        if self.include_ignored {
 979            self.snapshot.file_count()
 980        } else {
 981            self.snapshot.visible_file_count()
 982        }
 983    }
 984
 985    fn prefix(&self) -> Arc<str> {
 986        if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
 987            self.snapshot.root_name().into()
 988        } else if self.include_root_name {
 989            format!("{}/", self.snapshot.root_name()).into()
 990        } else {
 991            "".into()
 992        }
 993    }
 994
 995    fn candidates(&'a self, start: usize) -> Self::Candidates {
 996        CandidateSetIter {
 997            traversal: self.snapshot.files(self.include_ignored, start),
 998        }
 999    }
1000}
1001
1002struct CandidateSetIter<'a> {
1003    traversal: Traversal<'a>,
1004}
1005
1006impl<'a> Iterator for CandidateSetIter<'a> {
1007    type Item = PathMatchCandidate<'a>;
1008
1009    fn next(&mut self) -> Option<Self::Item> {
1010        self.traversal.next().map(|entry| {
1011            if let EntryKind::File(char_bag) = entry.kind {
1012                PathMatchCandidate {
1013                    path: &entry.path,
1014                    char_bag,
1015                }
1016            } else {
1017                unreachable!()
1018            }
1019        })
1020    }
1021}
1022
1023impl Entity for Project {
1024    type Event = Event;
1025
1026    fn release(&mut self, cx: &mut gpui::MutableAppContext) {
1027        match &self.client_state {
1028            ProjectClientState::Local { remote_id_rx, .. } => {
1029                if let Some(project_id) = *remote_id_rx.borrow() {
1030                    let rpc = self.client.clone();
1031                    cx.spawn(|_| async move {
1032                        if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
1033                            log::error!("error unregistering project: {}", err);
1034                        }
1035                    })
1036                    .detach();
1037                }
1038            }
1039            ProjectClientState::Remote { remote_id, .. } => {
1040                let rpc = self.client.clone();
1041                let project_id = *remote_id;
1042                cx.spawn(|_| async move {
1043                    if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
1044                        log::error!("error leaving project: {}", err);
1045                    }
1046                })
1047                .detach();
1048            }
1049        }
1050    }
1051}
1052
1053impl Collaborator {
1054    fn from_proto(
1055        message: proto::Collaborator,
1056        user_store: &ModelHandle<UserStore>,
1057        cx: &mut AsyncAppContext,
1058    ) -> impl Future<Output = Result<Self>> {
1059        let user = user_store.update(cx, |user_store, cx| {
1060            user_store.fetch_user(message.user_id, cx)
1061        });
1062
1063        async move {
1064            Ok(Self {
1065                peer_id: PeerId(message.peer_id),
1066                user: user.await?,
1067                replica_id: message.replica_id as ReplicaId,
1068            })
1069        }
1070    }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075    use super::*;
1076    use client::test::FakeHttpClient;
1077    use fs::RealFs;
1078    use gpui::TestAppContext;
1079    use language::LanguageRegistry;
1080    use serde_json::json;
1081    use std::{os::unix, path::PathBuf};
1082    use util::test::temp_tree;
1083
1084    #[gpui::test]
1085    async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1086        let dir = temp_tree(json!({
1087            "root": {
1088                "apple": "",
1089                "banana": {
1090                    "carrot": {
1091                        "date": "",
1092                        "endive": "",
1093                    }
1094                },
1095                "fennel": {
1096                    "grape": "",
1097                }
1098            }
1099        }));
1100
1101        let root_link_path = dir.path().join("root_link");
1102        unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1103        unix::fs::symlink(
1104            &dir.path().join("root/fennel"),
1105            &dir.path().join("root/finnochio"),
1106        )
1107        .unwrap();
1108
1109        let project = build_project(&mut cx);
1110
1111        let tree = project
1112            .update(&mut cx, |project, cx| {
1113                project.add_local_worktree(&root_link_path, cx)
1114            })
1115            .await
1116            .unwrap();
1117
1118        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1119            .await;
1120        cx.read(|cx| {
1121            let tree = tree.read(cx);
1122            assert_eq!(tree.file_count(), 5);
1123            assert_eq!(
1124                tree.inode_for_path("fennel/grape"),
1125                tree.inode_for_path("finnochio/grape")
1126            );
1127        });
1128
1129        let cancel_flag = Default::default();
1130        let results = project
1131            .read_with(&cx, |project, cx| {
1132                project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1133            })
1134            .await;
1135        assert_eq!(
1136            results
1137                .into_iter()
1138                .map(|result| result.path)
1139                .collect::<Vec<Arc<Path>>>(),
1140            vec![
1141                PathBuf::from("banana/carrot/date").into(),
1142                PathBuf::from("banana/carrot/endive").into(),
1143            ]
1144        );
1145    }
1146
1147    #[gpui::test]
1148    async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1149        let dir = temp_tree(json!({
1150            "root": {
1151                "dir1": {},
1152                "dir2": {
1153                    "dir3": {}
1154                }
1155            }
1156        }));
1157
1158        let project = build_project(&mut cx);
1159        let tree = project
1160            .update(&mut cx, |project, cx| {
1161                project.add_local_worktree(&dir.path(), cx)
1162            })
1163            .await
1164            .unwrap();
1165
1166        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1167            .await;
1168
1169        let cancel_flag = Default::default();
1170        let results = project
1171            .read_with(&cx, |project, cx| {
1172                project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1173            })
1174            .await;
1175
1176        assert!(results.is_empty());
1177    }
1178
1179    fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1180        let languages = Arc::new(LanguageRegistry::new());
1181        let fs = Arc::new(RealFs);
1182        let http_client = FakeHttpClient::with_404_response();
1183        let client = client::Client::new(http_client.clone());
1184        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1185        cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1186    }
1187}