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