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::{hash_map, HashMap, HashSet};
   9use futures::Future;
  10use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
  11use gpui::{
  12    AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
  13};
  14use language::{Buffer, DiagnosticEntry, Language, LanguageRegistry};
  15use lsp::{DiagnosticSeverity, LanguageServer};
  16use postage::{prelude::Stream, watch};
  17use smol::block_on;
  18use std::{
  19    path::{Path, PathBuf},
  20    sync::{atomic::AtomicBool, Arc},
  21};
  22use util::{ResultExt, TryFutureExt as _};
  23
  24pub use fs::*;
  25pub use worktree::*;
  26
  27pub struct Project {
  28    worktrees: Vec<ModelHandle<Worktree>>,
  29    active_entry: Option<ProjectEntry>,
  30    languages: Arc<LanguageRegistry>,
  31    language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
  32    client: Arc<client::Client>,
  33    user_store: ModelHandle<UserStore>,
  34    fs: Arc<dyn Fs>,
  35    client_state: ProjectClientState,
  36    collaborators: HashMap<PeerId, Collaborator>,
  37    subscriptions: Vec<client::Subscription>,
  38    language_servers_with_diagnostics_running: isize,
  39}
  40
  41enum ProjectClientState {
  42    Local {
  43        is_shared: bool,
  44        remote_id_tx: watch::Sender<Option<u64>>,
  45        remote_id_rx: watch::Receiver<Option<u64>>,
  46        _maintain_remote_id_task: Task<Option<()>>,
  47    },
  48    Remote {
  49        sharing_has_stopped: bool,
  50        remote_id: u64,
  51        replica_id: ReplicaId,
  52    },
  53}
  54
  55#[derive(Clone, Debug)]
  56pub struct Collaborator {
  57    pub user: Arc<User>,
  58    pub peer_id: PeerId,
  59    pub replica_id: ReplicaId,
  60}
  61
  62#[derive(Clone, Debug, PartialEq)]
  63pub enum Event {
  64    ActiveEntryChanged(Option<ProjectEntry>),
  65    WorktreeRemoved(WorktreeId),
  66    DiskBasedDiagnosticsStarted,
  67    DiskBasedDiagnosticsUpdated,
  68    DiskBasedDiagnosticsFinished,
  69    DiagnosticsUpdated(ProjectPath),
  70}
  71
  72#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
  73pub struct ProjectPath {
  74    pub worktree_id: WorktreeId,
  75    pub path: Arc<Path>,
  76}
  77
  78#[derive(Clone, Debug, Default, PartialEq)]
  79pub struct DiagnosticSummary {
  80    pub error_count: usize,
  81    pub warning_count: usize,
  82    pub info_count: usize,
  83    pub hint_count: usize,
  84}
  85
  86impl DiagnosticSummary {
  87    fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
  88        let mut this = Self {
  89            error_count: 0,
  90            warning_count: 0,
  91            info_count: 0,
  92            hint_count: 0,
  93        };
  94
  95        for entry in diagnostics {
  96            if entry.diagnostic.is_primary {
  97                match entry.diagnostic.severity {
  98                    DiagnosticSeverity::ERROR => this.error_count += 1,
  99                    DiagnosticSeverity::WARNING => this.warning_count += 1,
 100                    DiagnosticSeverity::INFORMATION => this.info_count += 1,
 101                    DiagnosticSeverity::HINT => this.hint_count += 1,
 102                    _ => {}
 103                }
 104            }
 105        }
 106
 107        this
 108    }
 109
 110    pub fn to_proto(&self, path: Arc<Path>) -> proto::DiagnosticSummary {
 111        proto::DiagnosticSummary {
 112            path: path.to_string_lossy().to_string(),
 113            error_count: self.error_count as u32,
 114            warning_count: self.warning_count as u32,
 115            info_count: self.info_count as u32,
 116            hint_count: self.hint_count as u32,
 117        }
 118    }
 119}
 120
 121#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 122pub struct ProjectEntry {
 123    pub worktree_id: WorktreeId,
 124    pub entry_id: usize,
 125}
 126
 127impl Project {
 128    pub fn local(
 129        client: Arc<Client>,
 130        user_store: ModelHandle<UserStore>,
 131        languages: Arc<LanguageRegistry>,
 132        fs: Arc<dyn Fs>,
 133        cx: &mut MutableAppContext,
 134    ) -> ModelHandle<Self> {
 135        cx.add_model(|cx: &mut ModelContext<Self>| {
 136            let (remote_id_tx, remote_id_rx) = watch::channel();
 137            let _maintain_remote_id_task = cx.spawn_weak({
 138                let rpc = client.clone();
 139                move |this, mut cx| {
 140                    async move {
 141                        let mut status = rpc.status();
 142                        while let Some(status) = status.recv().await {
 143                            if let Some(this) = this.upgrade(&cx) {
 144                                let remote_id = if let client::Status::Connected { .. } = status {
 145                                    let response = rpc.request(proto::RegisterProject {}).await?;
 146                                    Some(response.project_id)
 147                                } else {
 148                                    None
 149                                };
 150
 151                                if let Some(project_id) = remote_id {
 152                                    let mut registrations = Vec::new();
 153                                    this.read_with(&cx, |this, cx| {
 154                                        for worktree in &this.worktrees {
 155                                            let worktree_id = worktree.id() as u64;
 156                                            let worktree = worktree.read(cx).as_local().unwrap();
 157                                            registrations.push(rpc.request(
 158                                                proto::RegisterWorktree {
 159                                                    project_id,
 160                                                    worktree_id,
 161                                                    root_name: worktree.root_name().to_string(),
 162                                                    authorized_logins: worktree.authorized_logins(),
 163                                                },
 164                                            ));
 165                                        }
 166                                    });
 167                                    for registration in registrations {
 168                                        registration.await?;
 169                                    }
 170                                }
 171                                this.update(&mut cx, |this, cx| this.set_remote_id(remote_id, cx));
 172                            }
 173                        }
 174                        Ok(())
 175                    }
 176                    .log_err()
 177                }
 178            });
 179
 180            Self {
 181                worktrees: Default::default(),
 182                collaborators: Default::default(),
 183                client_state: ProjectClientState::Local {
 184                    is_shared: false,
 185                    remote_id_tx,
 186                    remote_id_rx,
 187                    _maintain_remote_id_task,
 188                },
 189                subscriptions: Vec::new(),
 190                active_entry: None,
 191                languages,
 192                client,
 193                user_store,
 194                fs,
 195                language_servers_with_diagnostics_running: 0,
 196                language_servers: Default::default(),
 197            }
 198        })
 199    }
 200
 201    pub async fn remote(
 202        remote_id: u64,
 203        client: Arc<Client>,
 204        user_store: ModelHandle<UserStore>,
 205        languages: Arc<LanguageRegistry>,
 206        fs: Arc<dyn Fs>,
 207        cx: &mut AsyncAppContext,
 208    ) -> Result<ModelHandle<Self>> {
 209        client.authenticate_and_connect(&cx).await?;
 210
 211        let response = client
 212            .request(proto::JoinProject {
 213                project_id: remote_id,
 214            })
 215            .await?;
 216
 217        let replica_id = response.replica_id as ReplicaId;
 218
 219        let mut worktrees = Vec::new();
 220        for worktree in response.worktrees {
 221            worktrees.push(
 222                Worktree::remote(
 223                    remote_id,
 224                    replica_id,
 225                    worktree,
 226                    client.clone(),
 227                    user_store.clone(),
 228                    cx,
 229                )
 230                .await?,
 231            );
 232        }
 233
 234        let user_ids = response
 235            .collaborators
 236            .iter()
 237            .map(|peer| peer.user_id)
 238            .collect();
 239        user_store
 240            .update(cx, |user_store, cx| user_store.load_users(user_ids, cx))
 241            .await?;
 242        let mut collaborators = HashMap::default();
 243        for message in response.collaborators {
 244            let collaborator = Collaborator::from_proto(message, &user_store, cx).await?;
 245            collaborators.insert(collaborator.peer_id, collaborator);
 246        }
 247
 248        Ok(cx.add_model(|cx| {
 249            let mut this = Self {
 250                worktrees: Vec::new(),
 251                active_entry: None,
 252                collaborators,
 253                languages,
 254                user_store,
 255                fs,
 256                subscriptions: vec![
 257                    client.subscribe_to_entity(remote_id, cx, Self::handle_unshare_project),
 258                    client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
 259                    client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
 260                    client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree),
 261                    client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree),
 262                    client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
 263                    client.subscribe_to_entity(
 264                        remote_id,
 265                        cx,
 266                        Self::handle_update_diagnostic_summary,
 267                    ),
 268                    client.subscribe_to_entity(
 269                        remote_id,
 270                        cx,
 271                        Self::handle_disk_based_diagnostics_updating,
 272                    ),
 273                    client.subscribe_to_entity(
 274                        remote_id,
 275                        cx,
 276                        Self::handle_disk_based_diagnostics_updated,
 277                    ),
 278                    client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
 279                    client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
 280                ],
 281                client,
 282                client_state: ProjectClientState::Remote {
 283                    sharing_has_stopped: false,
 284                    remote_id,
 285                    replica_id,
 286                },
 287                language_servers_with_diagnostics_running: 0,
 288                language_servers: Default::default(),
 289            };
 290            for worktree in worktrees {
 291                this.add_worktree(worktree, cx);
 292            }
 293            this
 294        }))
 295    }
 296
 297    fn set_remote_id(&mut self, remote_id: Option<u64>, cx: &mut ModelContext<Self>) {
 298        if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
 299            *remote_id_tx.borrow_mut() = remote_id;
 300        }
 301
 302        self.subscriptions.clear();
 303        if let Some(remote_id) = remote_id {
 304            let client = &self.client;
 305            self.subscriptions.extend([
 306                client.subscribe_to_entity(remote_id, cx, Self::handle_open_buffer),
 307                client.subscribe_to_entity(remote_id, cx, Self::handle_close_buffer),
 308                client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
 309                client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
 310                client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
 311                client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
 312                client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
 313                client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
 314                client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffer),
 315            ]);
 316        }
 317    }
 318
 319    pub fn remote_id(&self) -> Option<u64> {
 320        match &self.client_state {
 321            ProjectClientState::Local { remote_id_rx, .. } => *remote_id_rx.borrow(),
 322            ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
 323        }
 324    }
 325
 326    pub fn next_remote_id(&self) -> impl Future<Output = u64> {
 327        let mut id = None;
 328        let mut watch = None;
 329        match &self.client_state {
 330            ProjectClientState::Local { remote_id_rx, .. } => watch = Some(remote_id_rx.clone()),
 331            ProjectClientState::Remote { remote_id, .. } => id = Some(*remote_id),
 332        }
 333
 334        async move {
 335            if let Some(id) = id {
 336                return id;
 337            }
 338            let mut watch = watch.unwrap();
 339            loop {
 340                let id = *watch.borrow();
 341                if let Some(id) = id {
 342                    return id;
 343                }
 344                watch.recv().await;
 345            }
 346        }
 347    }
 348
 349    pub fn replica_id(&self) -> ReplicaId {
 350        match &self.client_state {
 351            ProjectClientState::Local { .. } => 0,
 352            ProjectClientState::Remote { replica_id, .. } => *replica_id,
 353        }
 354    }
 355
 356    pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
 357        &self.collaborators
 358    }
 359
 360    pub fn worktrees(&self) -> &[ModelHandle<Worktree>] {
 361        &self.worktrees
 362    }
 363
 364    pub fn worktree_for_id(
 365        &self,
 366        id: WorktreeId,
 367        cx: &AppContext,
 368    ) -> Option<ModelHandle<Worktree>> {
 369        self.worktrees
 370            .iter()
 371            .find(|worktree| worktree.read(cx).id() == id)
 372            .cloned()
 373    }
 374
 375    pub fn share(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
 376        let rpc = self.client.clone();
 377        cx.spawn(|this, mut cx| async move {
 378            let project_id = this.update(&mut cx, |this, _| {
 379                if let ProjectClientState::Local {
 380                    is_shared,
 381                    remote_id_rx,
 382                    ..
 383                } = &mut this.client_state
 384                {
 385                    *is_shared = true;
 386                    remote_id_rx
 387                        .borrow()
 388                        .ok_or_else(|| anyhow!("no project id"))
 389                } else {
 390                    Err(anyhow!("can't share a remote project"))
 391                }
 392            })?;
 393
 394            rpc.request(proto::ShareProject { project_id }).await?;
 395            let mut tasks = Vec::new();
 396            this.update(&mut cx, |this, cx| {
 397                for worktree in &this.worktrees {
 398                    worktree.update(cx, |worktree, cx| {
 399                        let worktree = worktree.as_local_mut().unwrap();
 400                        tasks.push(worktree.share(project_id, cx));
 401                    });
 402                }
 403            });
 404            for task in tasks {
 405                task.await?;
 406            }
 407            this.update(&mut cx, |_, cx| cx.notify());
 408            Ok(())
 409        })
 410    }
 411
 412    pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
 413        let rpc = self.client.clone();
 414        cx.spawn(|this, mut cx| async move {
 415            let project_id = this.update(&mut cx, |this, _| {
 416                if let ProjectClientState::Local {
 417                    is_shared,
 418                    remote_id_rx,
 419                    ..
 420                } = &mut this.client_state
 421                {
 422                    *is_shared = false;
 423                    remote_id_rx
 424                        .borrow()
 425                        .ok_or_else(|| anyhow!("no project id"))
 426                } else {
 427                    Err(anyhow!("can't share a remote project"))
 428                }
 429            })?;
 430
 431            rpc.send(proto::UnshareProject { project_id }).await?;
 432            this.update(&mut cx, |this, cx| {
 433                this.collaborators.clear();
 434                for worktree in &this.worktrees {
 435                    worktree.update(cx, |worktree, _| {
 436                        worktree.as_local_mut().unwrap().unshare();
 437                    });
 438                }
 439                cx.notify()
 440            });
 441            Ok(())
 442        })
 443    }
 444
 445    pub fn is_read_only(&self) -> bool {
 446        match &self.client_state {
 447            ProjectClientState::Local { .. } => false,
 448            ProjectClientState::Remote {
 449                sharing_has_stopped,
 450                ..
 451            } => *sharing_has_stopped,
 452        }
 453    }
 454
 455    pub fn is_local(&self) -> bool {
 456        match &self.client_state {
 457            ProjectClientState::Local { .. } => true,
 458            ProjectClientState::Remote { .. } => false,
 459        }
 460    }
 461
 462    pub fn open_buffer(
 463        &mut self,
 464        path: ProjectPath,
 465        cx: &mut ModelContext<Self>,
 466    ) -> Task<Result<ModelHandle<Buffer>>> {
 467        let worktree = if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
 468            worktree
 469        } else {
 470            return cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) });
 471        };
 472        let buffer_task = worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx));
 473        cx.spawn(|this, mut cx| async move {
 474            let (buffer, buffer_is_new) = buffer_task.await?;
 475            if buffer_is_new {
 476                this.update(&mut cx, |this, cx| {
 477                    this.assign_language_to_buffer(worktree, buffer.clone(), cx)
 478                });
 479            }
 480            Ok(buffer)
 481        })
 482    }
 483
 484    pub fn save_buffer_as(
 485        &self,
 486        buffer: ModelHandle<Buffer>,
 487        abs_path: PathBuf,
 488        cx: &mut ModelContext<Project>,
 489    ) -> Task<Result<()>> {
 490        let worktree_task = self.worktree_for_abs_path(&abs_path, cx);
 491        cx.spawn(|this, mut cx| async move {
 492            let (worktree, path) = worktree_task.await?;
 493            worktree
 494                .update(&mut cx, |worktree, cx| {
 495                    worktree
 496                        .as_local_mut()
 497                        .unwrap()
 498                        .save_buffer_as(buffer.clone(), path, cx)
 499                })
 500                .await?;
 501            this.update(&mut cx, |this, cx| {
 502                this.assign_language_to_buffer(worktree, buffer, cx)
 503            });
 504            Ok(())
 505        })
 506    }
 507
 508    fn assign_language_to_buffer(
 509        &mut self,
 510        worktree: ModelHandle<Worktree>,
 511        buffer: ModelHandle<Buffer>,
 512        cx: &mut ModelContext<Self>,
 513    ) -> Option<()> {
 514        // Set the buffer's language
 515        let full_path = buffer.read(cx).file()?.full_path();
 516        let language = self.languages.select_language(&full_path)?.clone();
 517        buffer.update(cx, |buffer, cx| {
 518            buffer.set_language(Some(language.clone()), cx);
 519        });
 520
 521        // For local worktrees, start a language server if needed.
 522        let worktree = worktree.read(cx);
 523        let worktree_id = worktree.id();
 524        let worktree_abs_path = worktree.as_local()?.abs_path().clone();
 525        let language_server = match self
 526            .language_servers
 527            .entry((worktree_id, language.name().to_string()))
 528        {
 529            hash_map::Entry::Occupied(e) => Some(e.get().clone()),
 530            hash_map::Entry::Vacant(e) => {
 531                Self::start_language_server(self.client.clone(), language, &worktree_abs_path, cx)
 532                    .map(|server| e.insert(server).clone())
 533            }
 534        };
 535
 536        buffer.update(cx, |buffer, cx| {
 537            buffer.set_language_server(language_server, cx)
 538        });
 539
 540        None
 541    }
 542
 543    fn start_language_server(
 544        rpc: Arc<Client>,
 545        language: Arc<Language>,
 546        worktree_path: &Path,
 547        cx: &mut ModelContext<Self>,
 548    ) -> Option<Arc<LanguageServer>> {
 549        enum LspEvent {
 550            DiagnosticsStart,
 551            DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
 552            DiagnosticsFinish,
 553        }
 554
 555        let language_server = language
 556            .start_server(worktree_path, cx)
 557            .log_err()
 558            .flatten()?;
 559        let disk_based_sources = language
 560            .disk_based_diagnostic_sources()
 561            .cloned()
 562            .unwrap_or_default();
 563        let disk_based_diagnostics_progress_token =
 564            language.disk_based_diagnostics_progress_token().cloned();
 565        let has_disk_based_diagnostic_progress_token =
 566            disk_based_diagnostics_progress_token.is_some();
 567        let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
 568
 569        // Listen for `PublishDiagnostics` notifications.
 570        language_server
 571            .on_notification::<lsp::notification::PublishDiagnostics, _>({
 572                let diagnostics_tx = diagnostics_tx.clone();
 573                move |params| {
 574                    if !has_disk_based_diagnostic_progress_token {
 575                        block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
 576                    }
 577                    block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))).ok();
 578                    if !has_disk_based_diagnostic_progress_token {
 579                        block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
 580                    }
 581                }
 582            })
 583            .detach();
 584
 585        // Listen for `Progress` notifications. Send an event when the language server
 586        // transitions between running jobs and not running any jobs.
 587        let mut running_jobs_for_this_server: i32 = 0;
 588        language_server
 589            .on_notification::<lsp::notification::Progress, _>(move |params| {
 590                let token = match params.token {
 591                    lsp::NumberOrString::Number(_) => None,
 592                    lsp::NumberOrString::String(token) => Some(token),
 593                };
 594
 595                if token == disk_based_diagnostics_progress_token {
 596                    match params.value {
 597                        lsp::ProgressParamsValue::WorkDone(progress) => match progress {
 598                            lsp::WorkDoneProgress::Begin(_) => {
 599                                running_jobs_for_this_server += 1;
 600                                if running_jobs_for_this_server == 1 {
 601                                    block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
 602                                }
 603                            }
 604                            lsp::WorkDoneProgress::End(_) => {
 605                                running_jobs_for_this_server -= 1;
 606                                if running_jobs_for_this_server == 0 {
 607                                    block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
 608                                }
 609                            }
 610                            _ => {}
 611                        },
 612                    }
 613                }
 614            })
 615            .detach();
 616
 617        // Process all the LSP events.
 618        cx.spawn_weak(|this, mut cx| async move {
 619            while let Ok(message) = diagnostics_rx.recv().await {
 620                let this = cx.read(|cx| this.upgrade(cx))?;
 621                match message {
 622                    LspEvent::DiagnosticsStart => {
 623                        let send = this.update(&mut cx, |this, cx| {
 624                            this.disk_based_diagnostics_started(cx);
 625                            this.remote_id().map(|project_id| {
 626                                rpc.send(proto::DiskBasedDiagnosticsUpdating { project_id })
 627                            })
 628                        });
 629                        if let Some(send) = send {
 630                            send.await.log_err();
 631                        }
 632                    }
 633                    LspEvent::DiagnosticsUpdate(params) => {
 634                        this.update(&mut cx, |this, cx| {
 635                            this.update_diagnostics(params, &disk_based_sources, cx)
 636                                .log_err();
 637                        });
 638                    }
 639                    LspEvent::DiagnosticsFinish => {
 640                        let send = this.update(&mut cx, |this, cx| {
 641                            this.disk_based_diagnostics_finished(cx);
 642                            this.remote_id().map(|project_id| {
 643                                rpc.send(proto::DiskBasedDiagnosticsUpdated { project_id })
 644                            })
 645                        });
 646                        if let Some(send) = send {
 647                            send.await.log_err();
 648                        }
 649                    }
 650                }
 651            }
 652            Some(())
 653        })
 654        .detach();
 655
 656        Some(language_server)
 657    }
 658
 659    fn update_diagnostics(
 660        &mut self,
 661        diagnostics: lsp::PublishDiagnosticsParams,
 662        disk_based_sources: &HashSet<String>,
 663        cx: &mut ModelContext<Self>,
 664    ) -> Result<()> {
 665        let path = diagnostics
 666            .uri
 667            .to_file_path()
 668            .map_err(|_| anyhow!("URI is not a file"))?;
 669        for tree in &self.worktrees {
 670            let relative_path = tree.update(cx, |tree, _| {
 671                path.strip_prefix(tree.as_local()?.abs_path()).ok()
 672            });
 673            if let Some(relative_path) = relative_path {
 674                let worktree_id = tree.read(cx).id();
 675                let project_path = ProjectPath {
 676                    worktree_id,
 677                    path: relative_path.into(),
 678                };
 679                tree.update(cx, |tree, cx| {
 680                    tree.as_local_mut().unwrap().update_diagnostics(
 681                        project_path.path.clone(),
 682                        diagnostics,
 683                        disk_based_sources,
 684                        cx,
 685                    )
 686                })?;
 687                cx.emit(Event::DiagnosticsUpdated(project_path));
 688                break;
 689            }
 690        }
 691        Ok(())
 692    }
 693
 694    pub fn worktree_for_abs_path(
 695        &self,
 696        abs_path: &Path,
 697        cx: &mut ModelContext<Self>,
 698    ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
 699        for tree in &self.worktrees {
 700            if let Some(relative_path) = tree
 701                .read(cx)
 702                .as_local()
 703                .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
 704            {
 705                return Task::ready(Ok((tree.clone(), relative_path.into())));
 706            }
 707        }
 708
 709        let worktree = self.add_local_worktree(abs_path, cx);
 710        cx.background().spawn(async move {
 711            let worktree = worktree.await?;
 712            Ok((worktree, PathBuf::new()))
 713        })
 714    }
 715
 716    pub fn is_shared(&self) -> bool {
 717        match &self.client_state {
 718            ProjectClientState::Local { is_shared, .. } => *is_shared,
 719            ProjectClientState::Remote { .. } => false,
 720        }
 721    }
 722
 723    pub fn add_local_worktree(
 724        &self,
 725        abs_path: impl AsRef<Path>,
 726        cx: &mut ModelContext<Self>,
 727    ) -> Task<Result<ModelHandle<Worktree>>> {
 728        let fs = self.fs.clone();
 729        let client = self.client.clone();
 730        let user_store = self.user_store.clone();
 731        let path = Arc::from(abs_path.as_ref());
 732        cx.spawn(|project, mut cx| async move {
 733            let worktree =
 734                Worktree::open_local(client.clone(), user_store, path, fs, &mut cx).await?;
 735
 736            let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
 737                project.add_worktree(worktree.clone(), cx);
 738                (project.remote_id(), project.is_shared())
 739            });
 740
 741            if let Some(project_id) = remote_project_id {
 742                let worktree_id = worktree.id() as u64;
 743                let register_message = worktree.update(&mut cx, |worktree, _| {
 744                    let worktree = worktree.as_local_mut().unwrap();
 745                    proto::RegisterWorktree {
 746                        project_id,
 747                        worktree_id,
 748                        root_name: worktree.root_name().to_string(),
 749                        authorized_logins: worktree.authorized_logins(),
 750                    }
 751                });
 752                client.request(register_message).await?;
 753                if is_shared {
 754                    worktree
 755                        .update(&mut cx, |worktree, cx| {
 756                            worktree.as_local_mut().unwrap().share(project_id, cx)
 757                        })
 758                        .await?;
 759                }
 760            }
 761
 762            Ok(worktree)
 763        })
 764    }
 765
 766    fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
 767        cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
 768        self.worktrees.push(worktree);
 769        cx.notify();
 770    }
 771
 772    pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
 773        let new_active_entry = entry.and_then(|project_path| {
 774            let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
 775            let entry = worktree.read(cx).entry_for_path(project_path.path)?;
 776            Some(ProjectEntry {
 777                worktree_id: project_path.worktree_id,
 778                entry_id: entry.id,
 779            })
 780        });
 781        if new_active_entry != self.active_entry {
 782            self.active_entry = new_active_entry;
 783            cx.emit(Event::ActiveEntryChanged(new_active_entry));
 784        }
 785    }
 786
 787    pub fn path_for_entry(&self, entry: ProjectEntry, cx: &AppContext) -> Option<ProjectPath> {
 788        let worktree = self.worktree_for_id(entry.worktree_id, cx)?.read(cx);
 789        Some(ProjectPath {
 790            worktree_id: entry.worktree_id,
 791            path: worktree.entry_for_id(entry.entry_id)?.path.clone(),
 792        })
 793    }
 794
 795    pub fn is_running_disk_based_diagnostics(&self) -> bool {
 796        self.language_servers_with_diagnostics_running > 0
 797    }
 798
 799    pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
 800        let mut summary = DiagnosticSummary::default();
 801        for (_, path_summary) in self.diagnostic_summaries(cx) {
 802            summary.error_count += path_summary.error_count;
 803            summary.warning_count += path_summary.warning_count;
 804            summary.info_count += path_summary.info_count;
 805            summary.hint_count += path_summary.hint_count;
 806        }
 807        summary
 808    }
 809
 810    pub fn diagnostic_summaries<'a>(
 811        &'a self,
 812        cx: &'a AppContext,
 813    ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
 814        self.worktrees.iter().flat_map(move |worktree| {
 815            let worktree = worktree.read(cx);
 816            let worktree_id = worktree.id();
 817            worktree
 818                .diagnostic_summaries()
 819                .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
 820        })
 821    }
 822
 823    fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
 824        self.language_servers_with_diagnostics_running += 1;
 825        if self.language_servers_with_diagnostics_running == 1 {
 826            cx.emit(Event::DiskBasedDiagnosticsStarted);
 827        }
 828    }
 829
 830    fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
 831        cx.emit(Event::DiskBasedDiagnosticsUpdated);
 832        self.language_servers_with_diagnostics_running -= 1;
 833        if self.language_servers_with_diagnostics_running == 0 {
 834            cx.emit(Event::DiskBasedDiagnosticsFinished);
 835        }
 836    }
 837
 838    pub fn active_entry(&self) -> Option<ProjectEntry> {
 839        self.active_entry
 840    }
 841
 842    // RPC message handlers
 843
 844    fn handle_unshare_project(
 845        &mut self,
 846        _: TypedEnvelope<proto::UnshareProject>,
 847        _: Arc<Client>,
 848        cx: &mut ModelContext<Self>,
 849    ) -> Result<()> {
 850        if let ProjectClientState::Remote {
 851            sharing_has_stopped,
 852            ..
 853        } = &mut self.client_state
 854        {
 855            *sharing_has_stopped = true;
 856            self.collaborators.clear();
 857            cx.notify();
 858            Ok(())
 859        } else {
 860            unreachable!()
 861        }
 862    }
 863
 864    fn handle_add_collaborator(
 865        &mut self,
 866        mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
 867        _: Arc<Client>,
 868        cx: &mut ModelContext<Self>,
 869    ) -> Result<()> {
 870        let user_store = self.user_store.clone();
 871        let collaborator = envelope
 872            .payload
 873            .collaborator
 874            .take()
 875            .ok_or_else(|| anyhow!("empty collaborator"))?;
 876
 877        cx.spawn(|this, mut cx| {
 878            async move {
 879                let collaborator =
 880                    Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
 881                this.update(&mut cx, |this, cx| {
 882                    this.collaborators
 883                        .insert(collaborator.peer_id, collaborator);
 884                    cx.notify();
 885                });
 886                Ok(())
 887            }
 888            .log_err()
 889        })
 890        .detach();
 891
 892        Ok(())
 893    }
 894
 895    fn handle_remove_collaborator(
 896        &mut self,
 897        envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
 898        _: Arc<Client>,
 899        cx: &mut ModelContext<Self>,
 900    ) -> Result<()> {
 901        let peer_id = PeerId(envelope.payload.peer_id);
 902        let replica_id = self
 903            .collaborators
 904            .remove(&peer_id)
 905            .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
 906            .replica_id;
 907        for worktree in &self.worktrees {
 908            worktree.update(cx, |worktree, cx| {
 909                worktree.remove_collaborator(peer_id, replica_id, cx);
 910            })
 911        }
 912        Ok(())
 913    }
 914
 915    fn handle_share_worktree(
 916        &mut self,
 917        envelope: TypedEnvelope<proto::ShareWorktree>,
 918        client: Arc<Client>,
 919        cx: &mut ModelContext<Self>,
 920    ) -> Result<()> {
 921        let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
 922        let replica_id = self.replica_id();
 923        let worktree = envelope
 924            .payload
 925            .worktree
 926            .ok_or_else(|| anyhow!("invalid worktree"))?;
 927        let user_store = self.user_store.clone();
 928        cx.spawn(|this, mut cx| {
 929            async move {
 930                let worktree =
 931                    Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx)
 932                        .await?;
 933                this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx));
 934                Ok(())
 935            }
 936            .log_err()
 937        })
 938        .detach();
 939        Ok(())
 940    }
 941
 942    fn handle_unregister_worktree(
 943        &mut self,
 944        envelope: TypedEnvelope<proto::UnregisterWorktree>,
 945        _: Arc<Client>,
 946        cx: &mut ModelContext<Self>,
 947    ) -> Result<()> {
 948        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 949        self.worktrees
 950            .retain(|worktree| worktree.read(cx).as_remote().unwrap().id() != worktree_id);
 951        cx.notify();
 952        Ok(())
 953    }
 954
 955    fn handle_update_worktree(
 956        &mut self,
 957        envelope: TypedEnvelope<proto::UpdateWorktree>,
 958        _: Arc<Client>,
 959        cx: &mut ModelContext<Self>,
 960    ) -> Result<()> {
 961        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 962        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 963            worktree.update(cx, |worktree, cx| {
 964                let worktree = worktree.as_remote_mut().unwrap();
 965                worktree.update_from_remote(envelope, cx)
 966            })?;
 967        }
 968        Ok(())
 969    }
 970
 971    fn handle_update_diagnostic_summary(
 972        &mut self,
 973        envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
 974        _: Arc<Client>,
 975        cx: &mut ModelContext<Self>,
 976    ) -> Result<()> {
 977        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 978        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
 979            if let Some(summary) = envelope.payload.summary {
 980                let project_path = ProjectPath {
 981                    worktree_id,
 982                    path: Path::new(&summary.path).into(),
 983                };
 984                worktree.update(cx, |worktree, _| {
 985                    worktree
 986                        .as_remote_mut()
 987                        .unwrap()
 988                        .update_diagnostic_summary(project_path.path.clone(), &summary);
 989                });
 990                cx.emit(Event::DiagnosticsUpdated(project_path));
 991            }
 992        }
 993        Ok(())
 994    }
 995
 996    fn handle_disk_based_diagnostics_updating(
 997        &mut self,
 998        _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
 999        _: Arc<Client>,
1000        cx: &mut ModelContext<Self>,
1001    ) -> Result<()> {
1002        self.disk_based_diagnostics_started(cx);
1003        Ok(())
1004    }
1005
1006    fn handle_disk_based_diagnostics_updated(
1007        &mut self,
1008        _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
1009        _: Arc<Client>,
1010        cx: &mut ModelContext<Self>,
1011    ) -> Result<()> {
1012        self.disk_based_diagnostics_finished(cx);
1013        Ok(())
1014    }
1015
1016    pub fn handle_update_buffer(
1017        &mut self,
1018        envelope: TypedEnvelope<proto::UpdateBuffer>,
1019        _: Arc<Client>,
1020        cx: &mut ModelContext<Self>,
1021    ) -> Result<()> {
1022        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1023        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1024            worktree.update(cx, |worktree, cx| {
1025                worktree.handle_update_buffer(envelope, cx)
1026            })?;
1027        }
1028        Ok(())
1029    }
1030
1031    pub fn handle_save_buffer(
1032        &mut self,
1033        envelope: TypedEnvelope<proto::SaveBuffer>,
1034        rpc: Arc<Client>,
1035        cx: &mut ModelContext<Self>,
1036    ) -> Result<()> {
1037        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1038        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1039            worktree.update(cx, |worktree, cx| {
1040                worktree.handle_save_buffer(envelope, rpc, cx)
1041            })?;
1042        }
1043        Ok(())
1044    }
1045
1046    pub fn handle_format_buffer(
1047        &mut self,
1048        envelope: TypedEnvelope<proto::FormatBuffer>,
1049        rpc: Arc<Client>,
1050        cx: &mut ModelContext<Self>,
1051    ) -> Result<()> {
1052        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1053        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1054            worktree.update(cx, |worktree, cx| {
1055                worktree.handle_format_buffer(envelope, rpc, cx)
1056            })?;
1057        }
1058        Ok(())
1059    }
1060
1061    pub fn handle_open_buffer(
1062        &mut self,
1063        envelope: TypedEnvelope<proto::OpenBuffer>,
1064        rpc: Arc<Client>,
1065        cx: &mut ModelContext<Self>,
1066    ) -> anyhow::Result<()> {
1067        let receipt = envelope.receipt();
1068        let peer_id = envelope.original_sender_id()?;
1069        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1070        let worktree = self
1071            .worktree_for_id(worktree_id, cx)
1072            .ok_or_else(|| anyhow!("no such worktree"))?;
1073
1074        let task = self.open_buffer(
1075            ProjectPath {
1076                worktree_id,
1077                path: PathBuf::from(envelope.payload.path).into(),
1078            },
1079            cx,
1080        );
1081        cx.spawn(|_, mut cx| {
1082            async move {
1083                let buffer = task.await?;
1084                let response = worktree.update(&mut cx, |worktree, cx| {
1085                    worktree
1086                        .as_local_mut()
1087                        .unwrap()
1088                        .open_remote_buffer(peer_id, buffer, cx)
1089                });
1090                rpc.respond(receipt, response).await?;
1091                Ok(())
1092            }
1093            .log_err()
1094        })
1095        .detach();
1096        Ok(())
1097    }
1098
1099    pub fn handle_close_buffer(
1100        &mut self,
1101        envelope: TypedEnvelope<proto::CloseBuffer>,
1102        _: Arc<Client>,
1103        cx: &mut ModelContext<Self>,
1104    ) -> anyhow::Result<()> {
1105        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1106        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1107            worktree.update(cx, |worktree, cx| {
1108                worktree
1109                    .as_local_mut()
1110                    .unwrap()
1111                    .close_remote_buffer(envelope, cx)
1112            })?;
1113        }
1114        Ok(())
1115    }
1116
1117    pub fn handle_buffer_saved(
1118        &mut self,
1119        envelope: TypedEnvelope<proto::BufferSaved>,
1120        _: Arc<Client>,
1121        cx: &mut ModelContext<Self>,
1122    ) -> Result<()> {
1123        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1124        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1125            worktree.update(cx, |worktree, cx| {
1126                worktree.handle_buffer_saved(envelope, cx)
1127            })?;
1128        }
1129        Ok(())
1130    }
1131
1132    pub fn match_paths<'a>(
1133        &self,
1134        query: &'a str,
1135        include_ignored: bool,
1136        smart_case: bool,
1137        max_results: usize,
1138        cancel_flag: &'a AtomicBool,
1139        cx: &AppContext,
1140    ) -> impl 'a + Future<Output = Vec<PathMatch>> {
1141        let include_root_name = self.worktrees.len() > 1;
1142        let candidate_sets = self
1143            .worktrees
1144            .iter()
1145            .map(|worktree| CandidateSet {
1146                snapshot: worktree.read(cx).snapshot(),
1147                include_ignored,
1148                include_root_name,
1149            })
1150            .collect::<Vec<_>>();
1151
1152        let background = cx.background().clone();
1153        async move {
1154            fuzzy::match_paths(
1155                candidate_sets.as_slice(),
1156                query,
1157                smart_case,
1158                max_results,
1159                cancel_flag,
1160                background,
1161            )
1162            .await
1163        }
1164    }
1165}
1166
1167struct CandidateSet {
1168    snapshot: Snapshot,
1169    include_ignored: bool,
1170    include_root_name: bool,
1171}
1172
1173impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
1174    type Candidates = CandidateSetIter<'a>;
1175
1176    fn id(&self) -> usize {
1177        self.snapshot.id().to_usize()
1178    }
1179
1180    fn len(&self) -> usize {
1181        if self.include_ignored {
1182            self.snapshot.file_count()
1183        } else {
1184            self.snapshot.visible_file_count()
1185        }
1186    }
1187
1188    fn prefix(&self) -> Arc<str> {
1189        if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
1190            self.snapshot.root_name().into()
1191        } else if self.include_root_name {
1192            format!("{}/", self.snapshot.root_name()).into()
1193        } else {
1194            "".into()
1195        }
1196    }
1197
1198    fn candidates(&'a self, start: usize) -> Self::Candidates {
1199        CandidateSetIter {
1200            traversal: self.snapshot.files(self.include_ignored, start),
1201        }
1202    }
1203}
1204
1205struct CandidateSetIter<'a> {
1206    traversal: Traversal<'a>,
1207}
1208
1209impl<'a> Iterator for CandidateSetIter<'a> {
1210    type Item = PathMatchCandidate<'a>;
1211
1212    fn next(&mut self) -> Option<Self::Item> {
1213        self.traversal.next().map(|entry| {
1214            if let EntryKind::File(char_bag) = entry.kind {
1215                PathMatchCandidate {
1216                    path: &entry.path,
1217                    char_bag,
1218                }
1219            } else {
1220                unreachable!()
1221            }
1222        })
1223    }
1224}
1225
1226impl Entity for Project {
1227    type Event = Event;
1228
1229    fn release(&mut self, cx: &mut gpui::MutableAppContext) {
1230        match &self.client_state {
1231            ProjectClientState::Local { remote_id_rx, .. } => {
1232                if let Some(project_id) = *remote_id_rx.borrow() {
1233                    let rpc = self.client.clone();
1234                    cx.spawn(|_| async move {
1235                        if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
1236                            log::error!("error unregistering project: {}", err);
1237                        }
1238                    })
1239                    .detach();
1240                }
1241            }
1242            ProjectClientState::Remote { remote_id, .. } => {
1243                let rpc = self.client.clone();
1244                let project_id = *remote_id;
1245                cx.spawn(|_| async move {
1246                    if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
1247                        log::error!("error leaving project: {}", err);
1248                    }
1249                })
1250                .detach();
1251            }
1252        }
1253    }
1254
1255    fn app_will_quit(
1256        &mut self,
1257        _: &mut MutableAppContext,
1258    ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
1259        use futures::FutureExt;
1260
1261        let shutdown_futures = self
1262            .language_servers
1263            .drain()
1264            .filter_map(|(_, server)| server.shutdown())
1265            .collect::<Vec<_>>();
1266        Some(
1267            async move {
1268                futures::future::join_all(shutdown_futures).await;
1269            }
1270            .boxed(),
1271        )
1272    }
1273}
1274
1275impl Collaborator {
1276    fn from_proto(
1277        message: proto::Collaborator,
1278        user_store: &ModelHandle<UserStore>,
1279        cx: &mut AsyncAppContext,
1280    ) -> impl Future<Output = Result<Self>> {
1281        let user = user_store.update(cx, |user_store, cx| {
1282            user_store.fetch_user(message.user_id, cx)
1283        });
1284
1285        async move {
1286            Ok(Self {
1287                peer_id: PeerId(message.peer_id),
1288                user: user.await?,
1289                replica_id: message.replica_id as ReplicaId,
1290            })
1291        }
1292    }
1293}
1294
1295#[cfg(test)]
1296mod tests {
1297    use super::{Event, *};
1298    use client::test::FakeHttpClient;
1299    use fs::RealFs;
1300    use futures::StreamExt;
1301    use gpui::{test::subscribe, TestAppContext};
1302    use language::{
1303        tree_sitter_rust, Diagnostic, LanguageConfig, LanguageRegistry, LanguageServerConfig, Point,
1304    };
1305    use lsp::Url;
1306    use serde_json::json;
1307    use std::{os::unix, path::PathBuf};
1308    use util::test::temp_tree;
1309
1310    #[gpui::test]
1311    async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1312        let dir = temp_tree(json!({
1313            "root": {
1314                "apple": "",
1315                "banana": {
1316                    "carrot": {
1317                        "date": "",
1318                        "endive": "",
1319                    }
1320                },
1321                "fennel": {
1322                    "grape": "",
1323                }
1324            }
1325        }));
1326
1327        let root_link_path = dir.path().join("root_link");
1328        unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1329        unix::fs::symlink(
1330            &dir.path().join("root/fennel"),
1331            &dir.path().join("root/finnochio"),
1332        )
1333        .unwrap();
1334
1335        let project = build_project(&mut cx);
1336
1337        let tree = project
1338            .update(&mut cx, |project, cx| {
1339                project.add_local_worktree(&root_link_path, cx)
1340            })
1341            .await
1342            .unwrap();
1343
1344        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1345            .await;
1346        cx.read(|cx| {
1347            let tree = tree.read(cx);
1348            assert_eq!(tree.file_count(), 5);
1349            assert_eq!(
1350                tree.inode_for_path("fennel/grape"),
1351                tree.inode_for_path("finnochio/grape")
1352            );
1353        });
1354
1355        let cancel_flag = Default::default();
1356        let results = project
1357            .read_with(&cx, |project, cx| {
1358                project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1359            })
1360            .await;
1361        assert_eq!(
1362            results
1363                .into_iter()
1364                .map(|result| result.path)
1365                .collect::<Vec<Arc<Path>>>(),
1366            vec![
1367                PathBuf::from("banana/carrot/date").into(),
1368                PathBuf::from("banana/carrot/endive").into(),
1369            ]
1370        );
1371    }
1372
1373    #[gpui::test]
1374    async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
1375        let (language_server_config, mut fake_server) =
1376            LanguageServerConfig::fake(cx.background()).await;
1377        let progress_token = language_server_config
1378            .disk_based_diagnostics_progress_token
1379            .clone()
1380            .unwrap();
1381
1382        let mut languages = LanguageRegistry::new();
1383        languages.add(Arc::new(Language::new(
1384            LanguageConfig {
1385                name: "Rust".to_string(),
1386                path_suffixes: vec!["rs".to_string()],
1387                language_server: Some(language_server_config),
1388                ..Default::default()
1389            },
1390            Some(tree_sitter_rust::language()),
1391        )));
1392
1393        let dir = temp_tree(json!({
1394            "a.rs": "fn a() { A }",
1395            "b.rs": "const y: i32 = 1",
1396        }));
1397
1398        let http_client = FakeHttpClient::with_404_response();
1399        let client = Client::new(http_client.clone());
1400        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1401
1402        let project = cx.update(|cx| {
1403            Project::local(
1404                client,
1405                user_store,
1406                Arc::new(languages),
1407                Arc::new(RealFs),
1408                cx,
1409            )
1410        });
1411
1412        let tree = project
1413            .update(&mut cx, |project, cx| {
1414                project.add_local_worktree(dir.path(), cx)
1415            })
1416            .await
1417            .unwrap();
1418        let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
1419
1420        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1421            .await;
1422
1423        // Cause worktree to start the fake language server
1424        let _buffer = project
1425            .update(&mut cx, |project, cx| {
1426                project.open_buffer(
1427                    ProjectPath {
1428                        worktree_id,
1429                        path: Path::new("b.rs").into(),
1430                    },
1431                    cx,
1432                )
1433            })
1434            .await
1435            .unwrap();
1436
1437        let mut events = subscribe(&project, &mut cx);
1438
1439        fake_server.start_progress(&progress_token).await;
1440        assert_eq!(
1441            events.next().await.unwrap(),
1442            Event::DiskBasedDiagnosticsStarted
1443        );
1444
1445        fake_server.start_progress(&progress_token).await;
1446        fake_server.end_progress(&progress_token).await;
1447        fake_server.start_progress(&progress_token).await;
1448
1449        fake_server
1450            .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
1451                uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
1452                version: None,
1453                diagnostics: vec![lsp::Diagnostic {
1454                    range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1455                    severity: Some(lsp::DiagnosticSeverity::ERROR),
1456                    message: "undefined variable 'A'".to_string(),
1457                    ..Default::default()
1458                }],
1459            })
1460            .await;
1461        assert_eq!(
1462            events.next().await.unwrap(),
1463            Event::DiagnosticsUpdated(ProjectPath {
1464                worktree_id,
1465                path: Arc::from(Path::new("a.rs"))
1466            })
1467        );
1468
1469        fake_server.end_progress(&progress_token).await;
1470        fake_server.end_progress(&progress_token).await;
1471        assert_eq!(
1472            events.next().await.unwrap(),
1473            Event::DiskBasedDiagnosticsUpdated
1474        );
1475        assert_eq!(
1476            events.next().await.unwrap(),
1477            Event::DiskBasedDiagnosticsFinished
1478        );
1479
1480        let (buffer, _) = tree
1481            .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx))
1482            .await
1483            .unwrap();
1484
1485        buffer.read_with(&cx, |buffer, _| {
1486            let snapshot = buffer.snapshot();
1487            let diagnostics = snapshot
1488                .diagnostics_in_range::<_, Point>(0..buffer.len())
1489                .collect::<Vec<_>>();
1490            assert_eq!(
1491                diagnostics,
1492                &[DiagnosticEntry {
1493                    range: Point::new(0, 9)..Point::new(0, 10),
1494                    diagnostic: Diagnostic {
1495                        severity: lsp::DiagnosticSeverity::ERROR,
1496                        message: "undefined variable 'A'".to_string(),
1497                        group_id: 0,
1498                        is_primary: true,
1499                        ..Default::default()
1500                    }
1501                }]
1502            )
1503        });
1504    }
1505
1506    #[gpui::test]
1507    async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1508        let dir = temp_tree(json!({
1509            "root": {
1510                "dir1": {},
1511                "dir2": {
1512                    "dir3": {}
1513                }
1514            }
1515        }));
1516
1517        let project = build_project(&mut cx);
1518        let tree = project
1519            .update(&mut cx, |project, cx| {
1520                project.add_local_worktree(&dir.path(), cx)
1521            })
1522            .await
1523            .unwrap();
1524
1525        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1526            .await;
1527
1528        let cancel_flag = Default::default();
1529        let results = project
1530            .read_with(&cx, |project, cx| {
1531                project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1532            })
1533            .await;
1534
1535        assert!(results.is_empty());
1536    }
1537
1538    fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1539        let languages = Arc::new(LanguageRegistry::new());
1540        let fs = Arc::new(RealFs);
1541        let http_client = FakeHttpClient::with_404_response();
1542        let client = client::Client::new(http_client.clone());
1543        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1544        cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1545    }
1546}