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