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