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    WeakModelHandle,
  14};
  15use language::{
  16    range_from_lsp, Bias, Buffer, Diagnostic, DiagnosticEntry, File as _, Language,
  17    LanguageRegistry, Operation, ToOffset, ToPointUtf16,
  18};
  19use lsp::{DiagnosticSeverity, LanguageServer};
  20use postage::{prelude::Stream, watch};
  21use smol::block_on;
  22use std::{
  23    convert::TryInto,
  24    ops::Range,
  25    path::{Path, PathBuf},
  26    sync::{
  27        atomic::{AtomicBool, Ordering::SeqCst},
  28        Arc,
  29    },
  30};
  31use util::{post_inc, ResultExt, TryFutureExt as _};
  32
  33pub use fs::*;
  34pub use worktree::*;
  35
  36pub struct Project {
  37    worktrees: Vec<WorktreeHandle>,
  38    active_entry: Option<ProjectEntry>,
  39    languages: Arc<LanguageRegistry>,
  40    language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
  41    client: Arc<client::Client>,
  42    user_store: ModelHandle<UserStore>,
  43    fs: Arc<dyn Fs>,
  44    client_state: ProjectClientState,
  45    collaborators: HashMap<PeerId, Collaborator>,
  46    subscriptions: Vec<client::Subscription>,
  47    language_servers_with_diagnostics_running: isize,
  48    open_buffers: HashMap<usize, OpenBuffer>,
  49    loading_buffers: HashMap<
  50        ProjectPath,
  51        postage::watch::Receiver<
  52            Option<Result<(ModelHandle<Buffer>, Arc<AtomicBool>), Arc<anyhow::Error>>>,
  53        >,
  54    >,
  55    shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>,
  56}
  57
  58enum OpenBuffer {
  59    Operations(Vec<Operation>),
  60    Loaded(WeakModelHandle<Buffer>),
  61}
  62
  63enum WorktreeHandle {
  64    Strong(ModelHandle<Worktree>),
  65    Weak(WeakModelHandle<Worktree>),
  66}
  67
  68enum ProjectClientState {
  69    Local {
  70        is_shared: bool,
  71        remote_id_tx: watch::Sender<Option<u64>>,
  72        remote_id_rx: watch::Receiver<Option<u64>>,
  73        _maintain_remote_id_task: Task<Option<()>>,
  74    },
  75    Remote {
  76        sharing_has_stopped: bool,
  77        remote_id: u64,
  78        replica_id: ReplicaId,
  79    },
  80}
  81
  82#[derive(Clone, Debug)]
  83pub struct Collaborator {
  84    pub user: Arc<User>,
  85    pub peer_id: PeerId,
  86    pub replica_id: ReplicaId,
  87}
  88
  89#[derive(Clone, Debug, PartialEq)]
  90pub enum Event {
  91    ActiveEntryChanged(Option<ProjectEntry>),
  92    WorktreeRemoved(WorktreeId),
  93    DiskBasedDiagnosticsStarted,
  94    DiskBasedDiagnosticsUpdated,
  95    DiskBasedDiagnosticsFinished,
  96    DiagnosticsUpdated(ProjectPath),
  97}
  98
  99#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
 100pub struct ProjectPath {
 101    pub worktree_id: WorktreeId,
 102    pub path: Arc<Path>,
 103}
 104
 105#[derive(Clone, Debug, Default, PartialEq)]
 106pub struct DiagnosticSummary {
 107    pub error_count: usize,
 108    pub warning_count: usize,
 109    pub info_count: usize,
 110    pub hint_count: usize,
 111}
 112
 113#[derive(Debug)]
 114pub struct Definition {
 115    pub target_buffer: ModelHandle<Buffer>,
 116    pub target_range: Range<language::Anchor>,
 117}
 118
 119impl DiagnosticSummary {
 120    fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
 121        let mut this = Self {
 122            error_count: 0,
 123            warning_count: 0,
 124            info_count: 0,
 125            hint_count: 0,
 126        };
 127
 128        for entry in diagnostics {
 129            if entry.diagnostic.is_primary {
 130                match entry.diagnostic.severity {
 131                    DiagnosticSeverity::ERROR => this.error_count += 1,
 132                    DiagnosticSeverity::WARNING => this.warning_count += 1,
 133                    DiagnosticSeverity::INFORMATION => this.info_count += 1,
 134                    DiagnosticSeverity::HINT => this.hint_count += 1,
 135                    _ => {}
 136                }
 137            }
 138        }
 139
 140        this
 141    }
 142
 143    pub fn to_proto(&self, path: Arc<Path>) -> proto::DiagnosticSummary {
 144        proto::DiagnosticSummary {
 145            path: path.to_string_lossy().to_string(),
 146            error_count: self.error_count as u32,
 147            warning_count: self.warning_count as u32,
 148            info_count: self.info_count as u32,
 149            hint_count: self.hint_count as u32,
 150        }
 151    }
 152}
 153
 154#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 155pub struct ProjectEntry {
 156    pub worktree_id: WorktreeId,
 157    pub entry_id: usize,
 158}
 159
 160impl Project {
 161    pub fn local(
 162        client: Arc<Client>,
 163        user_store: ModelHandle<UserStore>,
 164        languages: Arc<LanguageRegistry>,
 165        fs: Arc<dyn Fs>,
 166        cx: &mut MutableAppContext,
 167    ) -> ModelHandle<Self> {
 168        cx.add_model(|cx: &mut ModelContext<Self>| {
 169            let (remote_id_tx, remote_id_rx) = watch::channel();
 170            let _maintain_remote_id_task = cx.spawn_weak({
 171                let rpc = client.clone();
 172                move |this, mut cx| {
 173                    async move {
 174                        let mut status = rpc.status();
 175                        while let Some(status) = status.recv().await {
 176                            if let Some(this) = this.upgrade(&cx) {
 177                                let remote_id = if let client::Status::Connected { .. } = status {
 178                                    let response = rpc.request(proto::RegisterProject {}).await?;
 179                                    Some(response.project_id)
 180                                } else {
 181                                    None
 182                                };
 183
 184                                if let Some(project_id) = remote_id {
 185                                    let mut registrations = Vec::new();
 186                                    this.update(&mut cx, |this, cx| {
 187                                        for worktree in this.worktrees(cx).collect::<Vec<_>>() {
 188                                            registrations.push(worktree.update(
 189                                                cx,
 190                                                |worktree, cx| {
 191                                                    let worktree = worktree.as_local_mut().unwrap();
 192                                                    worktree.register(project_id, cx)
 193                                                },
 194                                            ));
 195                                        }
 196                                    });
 197                                    for registration in registrations {
 198                                        registration.await?;
 199                                    }
 200                                }
 201                                this.update(&mut cx, |this, cx| this.set_remote_id(remote_id, cx));
 202                            }
 203                        }
 204                        Ok(())
 205                    }
 206                    .log_err()
 207                }
 208            });
 209
 210            Self {
 211                worktrees: Default::default(),
 212                collaborators: Default::default(),
 213                open_buffers: Default::default(),
 214                loading_buffers: Default::default(),
 215                shared_buffers: Default::default(),
 216                client_state: ProjectClientState::Local {
 217                    is_shared: false,
 218                    remote_id_tx,
 219                    remote_id_rx,
 220                    _maintain_remote_id_task,
 221                },
 222                subscriptions: Vec::new(),
 223                active_entry: None,
 224                languages,
 225                client,
 226                user_store,
 227                fs,
 228                language_servers_with_diagnostics_running: 0,
 229                language_servers: Default::default(),
 230            }
 231        })
 232    }
 233
 234    pub async fn remote(
 235        remote_id: u64,
 236        client: Arc<Client>,
 237        user_store: ModelHandle<UserStore>,
 238        languages: Arc<LanguageRegistry>,
 239        fs: Arc<dyn Fs>,
 240        cx: &mut AsyncAppContext,
 241    ) -> Result<ModelHandle<Self>> {
 242        client.authenticate_and_connect(&cx).await?;
 243
 244        let response = client
 245            .request(proto::JoinProject {
 246                project_id: remote_id,
 247            })
 248            .await?;
 249
 250        let replica_id = response.replica_id as ReplicaId;
 251
 252        let mut worktrees = Vec::new();
 253        for worktree in response.worktrees {
 254            worktrees
 255                .push(Worktree::remote(remote_id, replica_id, worktree, client.clone(), cx).await?);
 256        }
 257
 258        let user_ids = response
 259            .collaborators
 260            .iter()
 261            .map(|peer| peer.user_id)
 262            .collect();
 263        user_store
 264            .update(cx, |user_store, cx| user_store.load_users(user_ids, cx))
 265            .await?;
 266        let mut collaborators = HashMap::default();
 267        for message in response.collaborators {
 268            let collaborator = Collaborator::from_proto(message, &user_store, cx).await?;
 269            collaborators.insert(collaborator.peer_id, collaborator);
 270        }
 271
 272        Ok(cx.add_model(|cx| {
 273            let mut this = Self {
 274                worktrees: Vec::new(),
 275                open_buffers: Default::default(),
 276                loading_buffers: Default::default(),
 277                shared_buffers: Default::default(),
 278                active_entry: None,
 279                collaborators,
 280                languages,
 281                user_store,
 282                fs,
 283                subscriptions: vec![
 284                    client.subscribe_to_entity(remote_id, cx, Self::handle_unshare_project),
 285                    client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
 286                    client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
 287                    client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree),
 288                    client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree),
 289                    client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
 290                    client.subscribe_to_entity(
 291                        remote_id,
 292                        cx,
 293                        Self::handle_update_diagnostic_summary,
 294                    ),
 295                    client.subscribe_to_entity(
 296                        remote_id,
 297                        cx,
 298                        Self::handle_disk_based_diagnostics_updating,
 299                    ),
 300                    client.subscribe_to_entity(
 301                        remote_id,
 302                        cx,
 303                        Self::handle_disk_based_diagnostics_updated,
 304                    ),
 305                    client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
 306                    client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
 307                ],
 308                client,
 309                client_state: ProjectClientState::Remote {
 310                    sharing_has_stopped: false,
 311                    remote_id,
 312                    replica_id,
 313                },
 314                language_servers_with_diagnostics_running: 0,
 315                language_servers: Default::default(),
 316            };
 317            for worktree in worktrees {
 318                this.add_worktree(&worktree, cx);
 319            }
 320            this
 321        }))
 322    }
 323
 324    fn set_remote_id(&mut self, remote_id: Option<u64>, cx: &mut ModelContext<Self>) {
 325        if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
 326            *remote_id_tx.borrow_mut() = remote_id;
 327        }
 328
 329        self.subscriptions.clear();
 330        if let Some(remote_id) = remote_id {
 331            let client = &self.client;
 332            self.subscriptions.extend([
 333                client.subscribe_to_entity(remote_id, cx, Self::handle_open_buffer),
 334                client.subscribe_to_entity(remote_id, cx, Self::handle_close_buffer),
 335                client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
 336                client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
 337                client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
 338                client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
 339                client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
 340                client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
 341                client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffer),
 342            ]);
 343        }
 344    }
 345
 346    pub fn remote_id(&self) -> Option<u64> {
 347        match &self.client_state {
 348            ProjectClientState::Local { remote_id_rx, .. } => *remote_id_rx.borrow(),
 349            ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
 350        }
 351    }
 352
 353    pub fn next_remote_id(&self) -> impl Future<Output = u64> {
 354        let mut id = None;
 355        let mut watch = None;
 356        match &self.client_state {
 357            ProjectClientState::Local { remote_id_rx, .. } => watch = Some(remote_id_rx.clone()),
 358            ProjectClientState::Remote { remote_id, .. } => id = Some(*remote_id),
 359        }
 360
 361        async move {
 362            if let Some(id) = id {
 363                return id;
 364            }
 365            let mut watch = watch.unwrap();
 366            loop {
 367                let id = *watch.borrow();
 368                if let Some(id) = id {
 369                    return id;
 370                }
 371                watch.recv().await;
 372            }
 373        }
 374    }
 375
 376    pub fn replica_id(&self) -> ReplicaId {
 377        match &self.client_state {
 378            ProjectClientState::Local { .. } => 0,
 379            ProjectClientState::Remote { replica_id, .. } => *replica_id,
 380        }
 381    }
 382
 383    pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
 384        &self.collaborators
 385    }
 386
 387    pub fn worktrees<'a>(
 388        &'a self,
 389        cx: &'a AppContext,
 390    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
 391        self.worktrees
 392            .iter()
 393            .filter_map(move |worktree| worktree.upgrade(cx))
 394    }
 395
 396    pub fn worktree_for_id(
 397        &self,
 398        id: WorktreeId,
 399        cx: &AppContext,
 400    ) -> Option<ModelHandle<Worktree>> {
 401        self.worktrees(cx)
 402            .find(|worktree| worktree.read(cx).id() == id)
 403    }
 404
 405    pub fn share(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
 406        let rpc = self.client.clone();
 407        cx.spawn(|this, mut cx| async move {
 408            let project_id = this.update(&mut cx, |this, _| {
 409                if let ProjectClientState::Local {
 410                    is_shared,
 411                    remote_id_rx,
 412                    ..
 413                } = &mut this.client_state
 414                {
 415                    *is_shared = true;
 416                    remote_id_rx
 417                        .borrow()
 418                        .ok_or_else(|| anyhow!("no project id"))
 419                } else {
 420                    Err(anyhow!("can't share a remote project"))
 421                }
 422            })?;
 423
 424            rpc.request(proto::ShareProject { project_id }).await?;
 425            let mut tasks = Vec::new();
 426            this.update(&mut cx, |this, cx| {
 427                for worktree in this.worktrees(cx).collect::<Vec<_>>() {
 428                    worktree.update(cx, |worktree, cx| {
 429                        let worktree = worktree.as_local_mut().unwrap();
 430                        tasks.push(worktree.share(cx));
 431                    });
 432                }
 433            });
 434            for task in tasks {
 435                task.await?;
 436            }
 437            this.update(&mut cx, |_, cx| cx.notify());
 438            Ok(())
 439        })
 440    }
 441
 442    pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
 443        let rpc = self.client.clone();
 444        cx.spawn(|this, mut cx| async move {
 445            let project_id = this.update(&mut cx, |this, _| {
 446                if let ProjectClientState::Local {
 447                    is_shared,
 448                    remote_id_rx,
 449                    ..
 450                } = &mut this.client_state
 451                {
 452                    *is_shared = false;
 453                    remote_id_rx
 454                        .borrow()
 455                        .ok_or_else(|| anyhow!("no project id"))
 456                } else {
 457                    Err(anyhow!("can't share a remote project"))
 458                }
 459            })?;
 460
 461            rpc.send(proto::UnshareProject { project_id }).await?;
 462            this.update(&mut cx, |this, cx| {
 463                this.collaborators.clear();
 464                for worktree in this.worktrees(cx).collect::<Vec<_>>() {
 465                    worktree.update(cx, |worktree, _| {
 466                        worktree.as_local_mut().unwrap().unshare();
 467                    });
 468                }
 469                cx.notify()
 470            });
 471            Ok(())
 472        })
 473    }
 474
 475    pub fn is_read_only(&self) -> bool {
 476        match &self.client_state {
 477            ProjectClientState::Local { .. } => false,
 478            ProjectClientState::Remote {
 479                sharing_has_stopped,
 480                ..
 481            } => *sharing_has_stopped,
 482        }
 483    }
 484
 485    pub fn is_local(&self) -> bool {
 486        match &self.client_state {
 487            ProjectClientState::Local { .. } => true,
 488            ProjectClientState::Remote { .. } => false,
 489        }
 490    }
 491
 492    pub fn open_buffer(
 493        &mut self,
 494        path: impl Into<ProjectPath>,
 495        cx: &mut ModelContext<Self>,
 496    ) -> Task<Result<ModelHandle<Buffer>>> {
 497        let path = path.into();
 498        let worktree = if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
 499            worktree
 500        } else {
 501            return cx
 502                .foreground()
 503                .spawn(async move { Err(anyhow!("no such worktree")) });
 504        };
 505
 506        // If there is already a buffer for the given path, then return it.
 507        let existing_buffer = self.get_open_buffer(&path, cx);
 508        if let Some(existing_buffer) = existing_buffer {
 509            return cx.foreground().spawn(async move { Ok(existing_buffer) });
 510        }
 511
 512        let mut loading_watch = match self.loading_buffers.entry(path.clone()) {
 513            // If the given path is already being loaded, then wait for that existing
 514            // task to complete and return the same buffer.
 515            hash_map::Entry::Occupied(e) => e.get().clone(),
 516
 517            // Otherwise, record the fact that this path is now being loaded.
 518            hash_map::Entry::Vacant(entry) => {
 519                let (mut tx, rx) = postage::watch::channel();
 520                entry.insert(rx.clone());
 521
 522                let load_buffer = worktree.update(cx, |worktree, cx| match worktree {
 523                    Worktree::Local(worktree) => worktree.open_buffer(&path.path, cx),
 524                    Worktree::Remote(worktree) => worktree.open_buffer(&path.path, cx),
 525                });
 526
 527                cx.spawn(move |this, mut cx| async move {
 528                    let load_result = load_buffer.await;
 529                    *tx.borrow_mut() = Some(this.update(&mut cx, |this, _| {
 530                        // Record the fact that the buffer is no longer loading.
 531                        this.loading_buffers.remove(&path);
 532                        let buffer = load_result.map_err(Arc::new)?;
 533                        this.open_buffers
 534                            .insert(buffer.id(), OpenBuffer::Loaded(buffer.downgrade()));
 535                        Ok((buffer, Arc::new(AtomicBool::new(true))))
 536                    }));
 537                })
 538                .detach();
 539                rx
 540            }
 541        };
 542
 543        cx.spawn(|this, mut cx| async move {
 544            let (buffer, buffer_is_new) = loop {
 545                if let Some(result) = loading_watch.borrow().as_ref() {
 546                    break match result {
 547                        Ok((buf, is_new)) => Ok((buf.clone(), is_new.fetch_and(false, SeqCst))),
 548                        Err(error) => Err(anyhow!("{}", error)),
 549                    };
 550                }
 551                loading_watch.recv().await;
 552            }?;
 553
 554            if buffer_is_new {
 555                this.update(&mut cx, |this, cx| {
 556                    this.assign_language_to_buffer(worktree, buffer.clone(), cx)
 557                });
 558            }
 559            Ok(buffer)
 560        })
 561    }
 562
 563    pub fn save_buffer_as(
 564        &self,
 565        buffer: ModelHandle<Buffer>,
 566        abs_path: PathBuf,
 567        cx: &mut ModelContext<Project>,
 568    ) -> Task<Result<()>> {
 569        let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, false, cx);
 570        cx.spawn(|this, mut cx| async move {
 571            let (worktree, path) = worktree_task.await?;
 572            worktree
 573                .update(&mut cx, |worktree, cx| {
 574                    worktree
 575                        .as_local_mut()
 576                        .unwrap()
 577                        .save_buffer_as(buffer.clone(), path, cx)
 578                })
 579                .await?;
 580            this.update(&mut cx, |this, cx| {
 581                this.open_buffers
 582                    .insert(buffer.id(), OpenBuffer::Loaded(buffer.downgrade()));
 583                this.assign_language_to_buffer(worktree, buffer, cx)
 584            });
 585            Ok(())
 586        })
 587    }
 588
 589    #[cfg(any(test, feature = "test-support"))]
 590    pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &AppContext) -> bool {
 591        let path = path.into();
 592        if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
 593            self.open_buffers.iter().any(|(_, buffer)| {
 594                if let Some(buffer) = buffer.upgrade(cx) {
 595                    if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
 596                        if file.worktree == worktree && file.path() == &path.path {
 597                            return true;
 598                        }
 599                    }
 600                }
 601                false
 602            })
 603        } else {
 604            false
 605        }
 606    }
 607
 608    fn get_open_buffer(
 609        &mut self,
 610        path: &ProjectPath,
 611        cx: &mut ModelContext<Self>,
 612    ) -> Option<ModelHandle<Buffer>> {
 613        let mut result = None;
 614        let worktree = self.worktree_for_id(path.worktree_id, cx)?;
 615        self.open_buffers.retain(|_, buffer| {
 616            if let OpenBuffer::Loaded(buffer) = buffer {
 617                if let Some(buffer) = buffer.upgrade(cx) {
 618                    if let Some(file) = File::from_dyn(buffer.read(cx).file()) {
 619                        if file.worktree == worktree && file.path() == &path.path {
 620                            result = Some(buffer);
 621                        }
 622                    }
 623                    return true;
 624                }
 625            }
 626            false
 627        });
 628        result
 629    }
 630
 631    fn assign_language_to_buffer(
 632        &mut self,
 633        worktree: ModelHandle<Worktree>,
 634        buffer: ModelHandle<Buffer>,
 635        cx: &mut ModelContext<Self>,
 636    ) -> Option<()> {
 637        // Set the buffer's language
 638        let full_path = buffer.read(cx).file()?.full_path();
 639        let language = self.languages.select_language(&full_path)?.clone();
 640        buffer.update(cx, |buffer, cx| {
 641            buffer.set_language(Some(language.clone()), cx);
 642        });
 643
 644        // For local worktrees, start a language server if needed.
 645        let worktree = worktree.read(cx);
 646        let worktree_id = worktree.id();
 647        let worktree_abs_path = worktree.as_local()?.abs_path().clone();
 648        let language_server = match self
 649            .language_servers
 650            .entry((worktree_id, language.name().to_string()))
 651        {
 652            hash_map::Entry::Occupied(e) => Some(e.get().clone()),
 653            hash_map::Entry::Vacant(e) => {
 654                Self::start_language_server(self.client.clone(), language, &worktree_abs_path, cx)
 655                    .map(|server| e.insert(server).clone())
 656            }
 657        };
 658
 659        buffer.update(cx, |buffer, cx| {
 660            buffer.set_language_server(language_server, cx)
 661        });
 662
 663        None
 664    }
 665
 666    fn start_language_server(
 667        rpc: Arc<Client>,
 668        language: Arc<Language>,
 669        worktree_path: &Path,
 670        cx: &mut ModelContext<Self>,
 671    ) -> Option<Arc<LanguageServer>> {
 672        enum LspEvent {
 673            DiagnosticsStart,
 674            DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
 675            DiagnosticsFinish,
 676        }
 677
 678        let language_server = language
 679            .start_server(worktree_path, cx)
 680            .log_err()
 681            .flatten()?;
 682        let disk_based_sources = language
 683            .disk_based_diagnostic_sources()
 684            .cloned()
 685            .unwrap_or_default();
 686        let disk_based_diagnostics_progress_token =
 687            language.disk_based_diagnostics_progress_token().cloned();
 688        let has_disk_based_diagnostic_progress_token =
 689            disk_based_diagnostics_progress_token.is_some();
 690        let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
 691
 692        // Listen for `PublishDiagnostics` notifications.
 693        language_server
 694            .on_notification::<lsp::notification::PublishDiagnostics, _>({
 695                let diagnostics_tx = diagnostics_tx.clone();
 696                move |params| {
 697                    if !has_disk_based_diagnostic_progress_token {
 698                        block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
 699                    }
 700                    block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))).ok();
 701                    if !has_disk_based_diagnostic_progress_token {
 702                        block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
 703                    }
 704                }
 705            })
 706            .detach();
 707
 708        // Listen for `Progress` notifications. Send an event when the language server
 709        // transitions between running jobs and not running any jobs.
 710        let mut running_jobs_for_this_server: i32 = 0;
 711        language_server
 712            .on_notification::<lsp::notification::Progress, _>(move |params| {
 713                let token = match params.token {
 714                    lsp::NumberOrString::Number(_) => None,
 715                    lsp::NumberOrString::String(token) => Some(token),
 716                };
 717
 718                if token == disk_based_diagnostics_progress_token {
 719                    match params.value {
 720                        lsp::ProgressParamsValue::WorkDone(progress) => match progress {
 721                            lsp::WorkDoneProgress::Begin(_) => {
 722                                running_jobs_for_this_server += 1;
 723                                if running_jobs_for_this_server == 1 {
 724                                    block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
 725                                }
 726                            }
 727                            lsp::WorkDoneProgress::End(_) => {
 728                                running_jobs_for_this_server -= 1;
 729                                if running_jobs_for_this_server == 0 {
 730                                    block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
 731                                }
 732                            }
 733                            _ => {}
 734                        },
 735                    }
 736                }
 737            })
 738            .detach();
 739
 740        // Process all the LSP events.
 741        cx.spawn_weak(|this, mut cx| async move {
 742            while let Ok(message) = diagnostics_rx.recv().await {
 743                let this = cx.read(|cx| this.upgrade(cx))?;
 744                match message {
 745                    LspEvent::DiagnosticsStart => {
 746                        let send = this.update(&mut cx, |this, cx| {
 747                            this.disk_based_diagnostics_started(cx);
 748                            this.remote_id().map(|project_id| {
 749                                rpc.send(proto::DiskBasedDiagnosticsUpdating { project_id })
 750                            })
 751                        });
 752                        if let Some(send) = send {
 753                            send.await.log_err();
 754                        }
 755                    }
 756                    LspEvent::DiagnosticsUpdate(params) => {
 757                        this.update(&mut cx, |this, cx| {
 758                            this.update_diagnostics(params, &disk_based_sources, cx)
 759                                .log_err();
 760                        });
 761                    }
 762                    LspEvent::DiagnosticsFinish => {
 763                        let send = this.update(&mut cx, |this, cx| {
 764                            this.disk_based_diagnostics_finished(cx);
 765                            this.remote_id().map(|project_id| {
 766                                rpc.send(proto::DiskBasedDiagnosticsUpdated { project_id })
 767                            })
 768                        });
 769                        if let Some(send) = send {
 770                            send.await.log_err();
 771                        }
 772                    }
 773                }
 774            }
 775            Some(())
 776        })
 777        .detach();
 778
 779        Some(language_server)
 780    }
 781
 782    pub fn update_diagnostics(
 783        &mut self,
 784        params: lsp::PublishDiagnosticsParams,
 785        disk_based_sources: &HashSet<String>,
 786        cx: &mut ModelContext<Self>,
 787    ) -> Result<()> {
 788        let path = params
 789            .uri
 790            .to_file_path()
 791            .map_err(|_| anyhow!("URI is not a file"))?;
 792        let (worktree, relative_path) = self
 793            .find_worktree_for_abs_path(&path, cx)
 794            .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?;
 795        let project_path = ProjectPath {
 796            worktree_id: worktree.read(cx).id(),
 797            path: relative_path.into(),
 798        };
 799        let mut next_group_id = 0;
 800        let mut diagnostics = Vec::default();
 801        let mut primary_diagnostic_group_ids = HashMap::default();
 802        let mut sources_by_group_id = HashMap::default();
 803        let mut supporting_diagnostic_severities = HashMap::default();
 804        for diagnostic in &params.diagnostics {
 805            let source = diagnostic.source.as_ref();
 806            let code = diagnostic.code.as_ref().map(|code| match code {
 807                lsp::NumberOrString::Number(code) => code.to_string(),
 808                lsp::NumberOrString::String(code) => code.clone(),
 809            });
 810            let range = range_from_lsp(diagnostic.range);
 811            let is_supporting = diagnostic
 812                .related_information
 813                .as_ref()
 814                .map_or(false, |infos| {
 815                    infos.iter().any(|info| {
 816                        primary_diagnostic_group_ids.contains_key(&(
 817                            source,
 818                            code.clone(),
 819                            range_from_lsp(info.location.range),
 820                        ))
 821                    })
 822                });
 823
 824            if is_supporting {
 825                if let Some(severity) = diagnostic.severity {
 826                    supporting_diagnostic_severities
 827                        .insert((source, code.clone(), range), severity);
 828                }
 829            } else {
 830                let group_id = post_inc(&mut next_group_id);
 831                let is_disk_based =
 832                    source.map_or(false, |source| disk_based_sources.contains(source));
 833
 834                sources_by_group_id.insert(group_id, source);
 835                primary_diagnostic_group_ids
 836                    .insert((source, code.clone(), range.clone()), group_id);
 837
 838                diagnostics.push(DiagnosticEntry {
 839                    range,
 840                    diagnostic: Diagnostic {
 841                        code: code.clone(),
 842                        severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR),
 843                        message: diagnostic.message.clone(),
 844                        group_id,
 845                        is_primary: true,
 846                        is_valid: true,
 847                        is_disk_based,
 848                    },
 849                });
 850                if let Some(infos) = &diagnostic.related_information {
 851                    for info in infos {
 852                        if info.location.uri == params.uri {
 853                            let range = range_from_lsp(info.location.range);
 854                            diagnostics.push(DiagnosticEntry {
 855                                range,
 856                                diagnostic: Diagnostic {
 857                                    code: code.clone(),
 858                                    severity: DiagnosticSeverity::INFORMATION,
 859                                    message: info.message.clone(),
 860                                    group_id,
 861                                    is_primary: false,
 862                                    is_valid: true,
 863                                    is_disk_based,
 864                                },
 865                            });
 866                        }
 867                    }
 868                }
 869            }
 870        }
 871
 872        for entry in &mut diagnostics {
 873            let diagnostic = &mut entry.diagnostic;
 874            if !diagnostic.is_primary {
 875                let source = *sources_by_group_id.get(&diagnostic.group_id).unwrap();
 876                if let Some(&severity) = supporting_diagnostic_severities.get(&(
 877                    source,
 878                    diagnostic.code.clone(),
 879                    entry.range.clone(),
 880                )) {
 881                    diagnostic.severity = severity;
 882                }
 883            }
 884        }
 885
 886        for buffer in self.open_buffers.values() {
 887            if let Some(buffer) = buffer.upgrade(cx) {
 888                if buffer
 889                    .read(cx)
 890                    .file()
 891                    .map_or(false, |file| *file.path() == project_path.path)
 892                {
 893                    buffer.update(cx, |buffer, cx| {
 894                        buffer.update_diagnostics(params.version, diagnostics.clone(), cx)
 895                    })?;
 896                    break;
 897                }
 898            }
 899        }
 900
 901        worktree.update(cx, |worktree, cx| {
 902            worktree
 903                .as_local_mut()
 904                .ok_or_else(|| anyhow!("not a local worktree"))?
 905                .update_diagnostics(project_path.path.clone(), diagnostics, cx)
 906        })?;
 907        cx.emit(Event::DiagnosticsUpdated(project_path));
 908        Ok(())
 909    }
 910
 911    pub fn definition<T: ToOffset>(
 912        &self,
 913        source_buffer_handle: &ModelHandle<Buffer>,
 914        position: T,
 915        cx: &mut ModelContext<Self>,
 916    ) -> Task<Result<Vec<Definition>>> {
 917        let source_buffer_handle = source_buffer_handle.clone();
 918        let buffer = source_buffer_handle.read(cx);
 919        let worktree;
 920        let buffer_abs_path;
 921        if let Some(file) = File::from_dyn(buffer.file()) {
 922            worktree = file.worktree.clone();
 923            buffer_abs_path = file.abs_path();
 924        } else {
 925            return Task::ready(Err(anyhow!("buffer does not belong to any worktree")));
 926        };
 927
 928        if worktree.read(cx).as_local().is_some() {
 929            let point = buffer.offset_to_point_utf16(position.to_offset(buffer));
 930            let buffer_abs_path = buffer_abs_path.unwrap();
 931            let lang_name;
 932            let lang_server;
 933            if let Some(lang) = buffer.language() {
 934                lang_name = lang.name().to_string();
 935                if let Some(server) = self
 936                    .language_servers
 937                    .get(&(worktree.read(cx).id(), lang_name.clone()))
 938                {
 939                    lang_server = server.clone();
 940                } else {
 941                    return Task::ready(Err(anyhow!("buffer does not have a language server")));
 942                };
 943            } else {
 944                return Task::ready(Err(anyhow!("buffer does not have a language")));
 945            }
 946
 947            cx.spawn(|this, mut cx| async move {
 948                let response = lang_server
 949                    .request::<lsp::request::GotoDefinition>(lsp::GotoDefinitionParams {
 950                        text_document_position_params: lsp::TextDocumentPositionParams {
 951                            text_document: lsp::TextDocumentIdentifier::new(
 952                                lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
 953                            ),
 954                            position: lsp::Position::new(point.row, point.column),
 955                        },
 956                        work_done_progress_params: Default::default(),
 957                        partial_result_params: Default::default(),
 958                    })
 959                    .await?;
 960
 961                let mut definitions = Vec::new();
 962                if let Some(response) = response {
 963                    let mut unresolved_locations = Vec::new();
 964                    match response {
 965                        lsp::GotoDefinitionResponse::Scalar(loc) => {
 966                            unresolved_locations.push((loc.uri, loc.range));
 967                        }
 968                        lsp::GotoDefinitionResponse::Array(locs) => {
 969                            unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range)));
 970                        }
 971                        lsp::GotoDefinitionResponse::Link(links) => {
 972                            unresolved_locations.extend(
 973                                links
 974                                    .into_iter()
 975                                    .map(|l| (l.target_uri, l.target_selection_range)),
 976                            );
 977                        }
 978                    }
 979
 980                    for (target_uri, target_range) in unresolved_locations {
 981                        let abs_path = target_uri
 982                            .to_file_path()
 983                            .map_err(|_| anyhow!("invalid target path"))?;
 984
 985                        let (worktree, relative_path) = if let Some(result) = this
 986                            .read_with(&cx, |this, cx| {
 987                                this.find_worktree_for_abs_path(&abs_path, cx)
 988                            }) {
 989                            result
 990                        } else {
 991                            let (worktree, relative_path) = this
 992                                .update(&mut cx, |this, cx| {
 993                                    this.create_worktree_for_abs_path(&abs_path, true, cx)
 994                                })
 995                                .await?;
 996                            this.update(&mut cx, |this, cx| {
 997                                this.language_servers.insert(
 998                                    (worktree.read(cx).id(), lang_name.clone()),
 999                                    lang_server.clone(),
1000                                );
1001                            });
1002                            (worktree, relative_path)
1003                        };
1004
1005                        let project_path = ProjectPath {
1006                            worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
1007                            path: relative_path.into(),
1008                        };
1009                        let target_buffer_handle = this
1010                            .update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
1011                            .await?;
1012                        cx.read(|cx| {
1013                            let target_buffer = target_buffer_handle.read(cx);
1014                            let target_start = target_buffer
1015                                .clip_point_utf16(target_range.start.to_point_utf16(), Bias::Left);
1016                            let target_end = target_buffer
1017                                .clip_point_utf16(target_range.end.to_point_utf16(), Bias::Left);
1018                            definitions.push(Definition {
1019                                target_buffer: target_buffer_handle,
1020                                target_range: target_buffer.anchor_after(target_start)
1021                                    ..target_buffer.anchor_before(target_end),
1022                            });
1023                        });
1024                    }
1025                }
1026
1027                Ok(definitions)
1028            })
1029        } else {
1030            log::info!("go to definition is not yet implemented for guests");
1031            Task::ready(Ok(Default::default()))
1032        }
1033    }
1034
1035    pub fn find_or_create_worktree_for_abs_path(
1036        &self,
1037        abs_path: impl AsRef<Path>,
1038        weak: bool,
1039        cx: &mut ModelContext<Self>,
1040    ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
1041        let abs_path = abs_path.as_ref();
1042        if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) {
1043            Task::ready(Ok((tree.clone(), relative_path.into())))
1044        } else {
1045            self.create_worktree_for_abs_path(abs_path, weak, cx)
1046        }
1047    }
1048
1049    fn create_worktree_for_abs_path(
1050        &self,
1051        abs_path: &Path,
1052        weak: bool,
1053        cx: &mut ModelContext<Self>,
1054    ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
1055        let worktree = self.add_local_worktree(abs_path, weak, cx);
1056        cx.background().spawn(async move {
1057            let worktree = worktree.await?;
1058            Ok((worktree, PathBuf::new()))
1059        })
1060    }
1061
1062    fn find_worktree_for_abs_path(
1063        &self,
1064        abs_path: &Path,
1065        cx: &AppContext,
1066    ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
1067        for tree in self.worktrees(cx) {
1068            if let Some(relative_path) = tree
1069                .read(cx)
1070                .as_local()
1071                .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
1072            {
1073                return Some((tree.clone(), relative_path.into()));
1074            }
1075        }
1076        None
1077    }
1078
1079    pub fn is_shared(&self) -> bool {
1080        match &self.client_state {
1081            ProjectClientState::Local { is_shared, .. } => *is_shared,
1082            ProjectClientState::Remote { .. } => false,
1083        }
1084    }
1085
1086    pub fn add_local_worktree(
1087        &self,
1088        abs_path: impl AsRef<Path>,
1089        weak: bool,
1090        cx: &mut ModelContext<Self>,
1091    ) -> Task<Result<ModelHandle<Worktree>>> {
1092        let fs = self.fs.clone();
1093        let client = self.client.clone();
1094        let path = Arc::from(abs_path.as_ref());
1095        cx.spawn(|project, mut cx| async move {
1096            let worktree = Worktree::local(client.clone(), path, weak, fs, &mut cx).await?;
1097
1098            let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
1099                project.add_worktree(&worktree, cx);
1100                (project.remote_id(), project.is_shared())
1101            });
1102
1103            if let Some(project_id) = remote_project_id {
1104                worktree
1105                    .update(&mut cx, |worktree, cx| {
1106                        worktree.as_local_mut().unwrap().register(project_id, cx)
1107                    })
1108                    .await?;
1109                if is_shared {
1110                    worktree
1111                        .update(&mut cx, |worktree, cx| {
1112                            worktree.as_local_mut().unwrap().share(cx)
1113                        })
1114                        .await?;
1115                }
1116            }
1117
1118            Ok(worktree)
1119        })
1120    }
1121
1122    pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext<Self>) {
1123        self.worktrees.retain(|worktree| {
1124            worktree
1125                .upgrade(cx)
1126                .map_or(false, |w| w.read(cx).id() != id)
1127        });
1128        cx.notify();
1129    }
1130
1131    fn add_worktree(&mut self, worktree: &ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
1132        cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
1133        cx.subscribe(&worktree, |this, worktree, _, cx| {
1134            this.update_open_buffers(worktree, cx)
1135        })
1136        .detach();
1137
1138        let push_weak_handle = {
1139            let worktree = worktree.read(cx);
1140            worktree.is_local() && worktree.is_weak()
1141        };
1142        if push_weak_handle {
1143            cx.observe_release(&worktree, |this, cx| {
1144                this.worktrees
1145                    .retain(|worktree| worktree.upgrade(cx).is_some());
1146                cx.notify();
1147            })
1148            .detach();
1149            self.worktrees
1150                .push(WorktreeHandle::Weak(worktree.downgrade()));
1151        } else {
1152            self.worktrees
1153                .push(WorktreeHandle::Strong(worktree.clone()));
1154        }
1155        cx.notify();
1156    }
1157
1158    fn update_open_buffers(
1159        &mut self,
1160        worktree_handle: ModelHandle<Worktree>,
1161        cx: &mut ModelContext<Self>,
1162    ) {
1163        let local = worktree_handle.read(cx).is_local();
1164        let snapshot = worktree_handle.read(cx).snapshot();
1165        let worktree_path = snapshot.abs_path();
1166        let mut buffers_to_delete = Vec::new();
1167        for (buffer_id, buffer) in &self.open_buffers {
1168            if let OpenBuffer::Loaded(buffer) = buffer {
1169                if let Some(buffer) = buffer.upgrade(cx) {
1170                    buffer.update(cx, |buffer, cx| {
1171                        if let Some(old_file) = File::from_dyn(buffer.file()) {
1172                            if old_file.worktree != worktree_handle {
1173                                return;
1174                            }
1175
1176                            let new_file = if let Some(entry) = old_file
1177                                .entry_id
1178                                .and_then(|entry_id| snapshot.entry_for_id(entry_id))
1179                            {
1180                                File {
1181                                    is_local: local,
1182                                    worktree_path: worktree_path.clone(),
1183                                    entry_id: Some(entry.id),
1184                                    mtime: entry.mtime,
1185                                    path: entry.path.clone(),
1186                                    worktree: worktree_handle.clone(),
1187                                }
1188                            } else if let Some(entry) =
1189                                snapshot.entry_for_path(old_file.path().as_ref())
1190                            {
1191                                File {
1192                                    is_local: local,
1193                                    worktree_path: worktree_path.clone(),
1194                                    entry_id: Some(entry.id),
1195                                    mtime: entry.mtime,
1196                                    path: entry.path.clone(),
1197                                    worktree: worktree_handle.clone(),
1198                                }
1199                            } else {
1200                                File {
1201                                    is_local: local,
1202                                    worktree_path: worktree_path.clone(),
1203                                    entry_id: None,
1204                                    path: old_file.path().clone(),
1205                                    mtime: old_file.mtime(),
1206                                    worktree: worktree_handle.clone(),
1207                                }
1208                            };
1209
1210                            if let Some(task) = buffer.file_updated(Box::new(new_file), cx) {
1211                                task.detach();
1212                            }
1213                        }
1214                    });
1215                } else {
1216                    buffers_to_delete.push(*buffer_id);
1217                }
1218            }
1219        }
1220
1221        for buffer_id in buffers_to_delete {
1222            self.open_buffers.remove(&buffer_id);
1223        }
1224    }
1225
1226    pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
1227        let new_active_entry = entry.and_then(|project_path| {
1228            let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
1229            let entry = worktree.read(cx).entry_for_path(project_path.path)?;
1230            Some(ProjectEntry {
1231                worktree_id: project_path.worktree_id,
1232                entry_id: entry.id,
1233            })
1234        });
1235        if new_active_entry != self.active_entry {
1236            self.active_entry = new_active_entry;
1237            cx.emit(Event::ActiveEntryChanged(new_active_entry));
1238        }
1239    }
1240
1241    pub fn is_running_disk_based_diagnostics(&self) -> bool {
1242        self.language_servers_with_diagnostics_running > 0
1243    }
1244
1245    pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
1246        let mut summary = DiagnosticSummary::default();
1247        for (_, path_summary) in self.diagnostic_summaries(cx) {
1248            summary.error_count += path_summary.error_count;
1249            summary.warning_count += path_summary.warning_count;
1250            summary.info_count += path_summary.info_count;
1251            summary.hint_count += path_summary.hint_count;
1252        }
1253        summary
1254    }
1255
1256    pub fn diagnostic_summaries<'a>(
1257        &'a self,
1258        cx: &'a AppContext,
1259    ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
1260        self.worktrees(cx).flat_map(move |worktree| {
1261            let worktree = worktree.read(cx);
1262            let worktree_id = worktree.id();
1263            worktree
1264                .diagnostic_summaries()
1265                .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
1266        })
1267    }
1268
1269    fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
1270        self.language_servers_with_diagnostics_running += 1;
1271        if self.language_servers_with_diagnostics_running == 1 {
1272            cx.emit(Event::DiskBasedDiagnosticsStarted);
1273        }
1274    }
1275
1276    fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
1277        cx.emit(Event::DiskBasedDiagnosticsUpdated);
1278        self.language_servers_with_diagnostics_running -= 1;
1279        if self.language_servers_with_diagnostics_running == 0 {
1280            cx.emit(Event::DiskBasedDiagnosticsFinished);
1281        }
1282    }
1283
1284    pub fn active_entry(&self) -> Option<ProjectEntry> {
1285        self.active_entry
1286    }
1287
1288    // RPC message handlers
1289
1290    fn handle_unshare_project(
1291        &mut self,
1292        _: TypedEnvelope<proto::UnshareProject>,
1293        _: Arc<Client>,
1294        cx: &mut ModelContext<Self>,
1295    ) -> Result<()> {
1296        if let ProjectClientState::Remote {
1297            sharing_has_stopped,
1298            ..
1299        } = &mut self.client_state
1300        {
1301            *sharing_has_stopped = true;
1302            self.collaborators.clear();
1303            cx.notify();
1304            Ok(())
1305        } else {
1306            unreachable!()
1307        }
1308    }
1309
1310    fn handle_add_collaborator(
1311        &mut self,
1312        mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
1313        _: Arc<Client>,
1314        cx: &mut ModelContext<Self>,
1315    ) -> Result<()> {
1316        let user_store = self.user_store.clone();
1317        let collaborator = envelope
1318            .payload
1319            .collaborator
1320            .take()
1321            .ok_or_else(|| anyhow!("empty collaborator"))?;
1322
1323        cx.spawn(|this, mut cx| {
1324            async move {
1325                let collaborator =
1326                    Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
1327                this.update(&mut cx, |this, cx| {
1328                    this.collaborators
1329                        .insert(collaborator.peer_id, collaborator);
1330                    cx.notify();
1331                });
1332                Ok(())
1333            }
1334            .log_err()
1335        })
1336        .detach();
1337
1338        Ok(())
1339    }
1340
1341    fn handle_remove_collaborator(
1342        &mut self,
1343        envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
1344        _: Arc<Client>,
1345        cx: &mut ModelContext<Self>,
1346    ) -> Result<()> {
1347        let peer_id = PeerId(envelope.payload.peer_id);
1348        let replica_id = self
1349            .collaborators
1350            .remove(&peer_id)
1351            .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
1352            .replica_id;
1353        self.shared_buffers.remove(&peer_id);
1354        for (_, buffer) in &self.open_buffers {
1355            if let OpenBuffer::Loaded(buffer) = buffer {
1356                if let Some(buffer) = buffer.upgrade(cx) {
1357                    buffer.update(cx, |buffer, cx| buffer.remove_peer(replica_id, cx));
1358                }
1359            }
1360        }
1361        cx.notify();
1362        Ok(())
1363    }
1364
1365    fn handle_share_worktree(
1366        &mut self,
1367        envelope: TypedEnvelope<proto::ShareWorktree>,
1368        client: Arc<Client>,
1369        cx: &mut ModelContext<Self>,
1370    ) -> Result<()> {
1371        let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
1372        let replica_id = self.replica_id();
1373        let worktree = envelope
1374            .payload
1375            .worktree
1376            .ok_or_else(|| anyhow!("invalid worktree"))?;
1377        cx.spawn(|this, mut cx| {
1378            async move {
1379                let worktree =
1380                    Worktree::remote(remote_id, replica_id, worktree, client, &mut cx).await?;
1381                this.update(&mut cx, |this, cx| this.add_worktree(&worktree, cx));
1382                Ok(())
1383            }
1384            .log_err()
1385        })
1386        .detach();
1387        Ok(())
1388    }
1389
1390    fn handle_unregister_worktree(
1391        &mut self,
1392        envelope: TypedEnvelope<proto::UnregisterWorktree>,
1393        _: Arc<Client>,
1394        cx: &mut ModelContext<Self>,
1395    ) -> Result<()> {
1396        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1397        self.remove_worktree(worktree_id, cx);
1398        Ok(())
1399    }
1400
1401    fn handle_update_worktree(
1402        &mut self,
1403        envelope: TypedEnvelope<proto::UpdateWorktree>,
1404        _: Arc<Client>,
1405        cx: &mut ModelContext<Self>,
1406    ) -> Result<()> {
1407        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1408        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1409            worktree.update(cx, |worktree, cx| {
1410                let worktree = worktree.as_remote_mut().unwrap();
1411                worktree.update_from_remote(envelope, cx)
1412            })?;
1413        }
1414        Ok(())
1415    }
1416
1417    fn handle_update_diagnostic_summary(
1418        &mut self,
1419        envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
1420        _: Arc<Client>,
1421        cx: &mut ModelContext<Self>,
1422    ) -> Result<()> {
1423        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1424        if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1425            if let Some(summary) = envelope.payload.summary {
1426                let project_path = ProjectPath {
1427                    worktree_id,
1428                    path: Path::new(&summary.path).into(),
1429                };
1430                worktree.update(cx, |worktree, _| {
1431                    worktree
1432                        .as_remote_mut()
1433                        .unwrap()
1434                        .update_diagnostic_summary(project_path.path.clone(), &summary);
1435                });
1436                cx.emit(Event::DiagnosticsUpdated(project_path));
1437            }
1438        }
1439        Ok(())
1440    }
1441
1442    fn handle_disk_based_diagnostics_updating(
1443        &mut self,
1444        _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
1445        _: Arc<Client>,
1446        cx: &mut ModelContext<Self>,
1447    ) -> Result<()> {
1448        self.disk_based_diagnostics_started(cx);
1449        Ok(())
1450    }
1451
1452    fn handle_disk_based_diagnostics_updated(
1453        &mut self,
1454        _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
1455        _: Arc<Client>,
1456        cx: &mut ModelContext<Self>,
1457    ) -> Result<()> {
1458        self.disk_based_diagnostics_finished(cx);
1459        Ok(())
1460    }
1461
1462    pub fn handle_update_buffer(
1463        &mut self,
1464        envelope: TypedEnvelope<proto::UpdateBuffer>,
1465        _: Arc<Client>,
1466        cx: &mut ModelContext<Self>,
1467    ) -> Result<()> {
1468        let payload = envelope.payload.clone();
1469        let buffer_id = payload.buffer_id as usize;
1470        let ops = payload
1471            .operations
1472            .into_iter()
1473            .map(|op| language::proto::deserialize_operation(op))
1474            .collect::<Result<Vec<_>, _>>()?;
1475        match self.open_buffers.get_mut(&buffer_id) {
1476            Some(OpenBuffer::Operations(pending_ops)) => pending_ops.extend(ops),
1477            Some(OpenBuffer::Loaded(buffer)) => {
1478                if let Some(buffer) = buffer.upgrade(cx) {
1479                    buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))?;
1480                } else {
1481                    self.open_buffers
1482                        .insert(buffer_id, OpenBuffer::Operations(ops));
1483                }
1484            }
1485            None => {
1486                self.open_buffers
1487                    .insert(buffer_id, OpenBuffer::Operations(ops));
1488            }
1489        }
1490        Ok(())
1491    }
1492
1493    pub fn handle_save_buffer(
1494        &mut self,
1495        envelope: TypedEnvelope<proto::SaveBuffer>,
1496        rpc: Arc<Client>,
1497        cx: &mut ModelContext<Self>,
1498    ) -> Result<()> {
1499        let sender_id = envelope.original_sender_id()?;
1500        let project_id = self.remote_id().ok_or_else(|| anyhow!("not connected"))?;
1501        let buffer = self
1502            .shared_buffers
1503            .get(&sender_id)
1504            .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1505            .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1506        let receipt = envelope.receipt();
1507        let buffer_id = envelope.payload.buffer_id;
1508        let save = cx.spawn(|_, mut cx| async move {
1509            buffer.update(&mut cx, |buffer, cx| buffer.save(cx)).await
1510        });
1511
1512        cx.background()
1513            .spawn(
1514                async move {
1515                    let (version, mtime) = save.await?;
1516
1517                    rpc.respond(
1518                        receipt,
1519                        proto::BufferSaved {
1520                            project_id,
1521                            buffer_id,
1522                            version: (&version).into(),
1523                            mtime: Some(mtime.into()),
1524                        },
1525                    )
1526                    .await?;
1527
1528                    Ok(())
1529                }
1530                .log_err(),
1531            )
1532            .detach();
1533        Ok(())
1534    }
1535
1536    pub fn handle_format_buffer(
1537        &mut self,
1538        envelope: TypedEnvelope<proto::FormatBuffer>,
1539        rpc: Arc<Client>,
1540        cx: &mut ModelContext<Self>,
1541    ) -> Result<()> {
1542        let receipt = envelope.receipt();
1543        let sender_id = envelope.original_sender_id()?;
1544        let buffer = self
1545            .shared_buffers
1546            .get(&sender_id)
1547            .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
1548            .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
1549        cx.spawn(|_, mut cx| async move {
1550            let format = buffer.update(&mut cx, |buffer, cx| buffer.format(cx)).await;
1551            // We spawn here in order to enqueue the sending of `Ack` *after* transmission of edits
1552            // associated with formatting.
1553            cx.spawn(|_| async move {
1554                match format {
1555                    Ok(()) => rpc.respond(receipt, proto::Ack {}).await?,
1556                    Err(error) => {
1557                        rpc.respond_with_error(
1558                            receipt,
1559                            proto::Error {
1560                                message: error.to_string(),
1561                            },
1562                        )
1563                        .await?
1564                    }
1565                }
1566                Ok::<_, anyhow::Error>(())
1567            })
1568            .await
1569            .log_err();
1570        })
1571        .detach();
1572        Ok(())
1573    }
1574
1575    pub fn handle_open_buffer(
1576        &mut self,
1577        envelope: TypedEnvelope<proto::OpenBuffer>,
1578        rpc: Arc<Client>,
1579        cx: &mut ModelContext<Self>,
1580    ) -> anyhow::Result<()> {
1581        let receipt = envelope.receipt();
1582        let peer_id = envelope.original_sender_id()?;
1583        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1584        let open_buffer = self.open_buffer(
1585            ProjectPath {
1586                worktree_id,
1587                path: PathBuf::from(envelope.payload.path).into(),
1588            },
1589            cx,
1590        );
1591        cx.spawn(|this, mut cx| {
1592            async move {
1593                let buffer = open_buffer.await?;
1594                this.update(&mut cx, |this, _| {
1595                    this.shared_buffers
1596                        .entry(peer_id)
1597                        .or_default()
1598                        .insert(buffer.id() as u64, buffer.clone());
1599                });
1600                let message = buffer.read_with(&cx, |buffer, _| buffer.to_proto());
1601                rpc.respond(
1602                    receipt,
1603                    proto::OpenBufferResponse {
1604                        buffer: Some(message),
1605                    },
1606                )
1607                .await
1608            }
1609            .log_err()
1610        })
1611        .detach();
1612        Ok(())
1613    }
1614
1615    pub fn handle_close_buffer(
1616        &mut self,
1617        envelope: TypedEnvelope<proto::CloseBuffer>,
1618        _: Arc<Client>,
1619        cx: &mut ModelContext<Self>,
1620    ) -> anyhow::Result<()> {
1621        if let Some(shared_buffers) = self.shared_buffers.get_mut(&envelope.original_sender_id()?) {
1622            shared_buffers.remove(&envelope.payload.buffer_id);
1623            cx.notify();
1624        }
1625        Ok(())
1626    }
1627
1628    pub fn handle_buffer_saved(
1629        &mut self,
1630        envelope: TypedEnvelope<proto::BufferSaved>,
1631        _: Arc<Client>,
1632        cx: &mut ModelContext<Self>,
1633    ) -> Result<()> {
1634        let payload = envelope.payload.clone();
1635        let buffer = self
1636            .open_buffers
1637            .get(&(payload.buffer_id as usize))
1638            .and_then(|buf| {
1639                if let OpenBuffer::Loaded(buffer) = buf {
1640                    buffer.upgrade(cx)
1641                } else {
1642                    None
1643                }
1644            });
1645        if let Some(buffer) = buffer {
1646            buffer.update(cx, |buffer, cx| {
1647                let version = payload.version.try_into()?;
1648                let mtime = payload
1649                    .mtime
1650                    .ok_or_else(|| anyhow!("missing mtime"))?
1651                    .into();
1652                buffer.did_save(version, mtime, None, cx);
1653                Result::<_, anyhow::Error>::Ok(())
1654            })?;
1655        }
1656        Ok(())
1657    }
1658
1659    pub fn match_paths<'a>(
1660        &self,
1661        query: &'a str,
1662        include_ignored: bool,
1663        smart_case: bool,
1664        max_results: usize,
1665        cancel_flag: &'a AtomicBool,
1666        cx: &AppContext,
1667    ) -> impl 'a + Future<Output = Vec<PathMatch>> {
1668        let worktrees = self
1669            .worktrees(cx)
1670            .filter(|worktree| !worktree.read(cx).is_weak())
1671            .collect::<Vec<_>>();
1672        let include_root_name = worktrees.len() > 1;
1673        let candidate_sets = worktrees
1674            .into_iter()
1675            .map(|worktree| CandidateSet {
1676                snapshot: worktree.read(cx).snapshot(),
1677                include_ignored,
1678                include_root_name,
1679            })
1680            .collect::<Vec<_>>();
1681
1682        let background = cx.background().clone();
1683        async move {
1684            fuzzy::match_paths(
1685                candidate_sets.as_slice(),
1686                query,
1687                smart_case,
1688                max_results,
1689                cancel_flag,
1690                background,
1691            )
1692            .await
1693        }
1694    }
1695}
1696
1697impl WorktreeHandle {
1698    pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
1699        match self {
1700            WorktreeHandle::Strong(handle) => Some(handle.clone()),
1701            WorktreeHandle::Weak(handle) => handle.upgrade(cx),
1702        }
1703    }
1704}
1705
1706struct CandidateSet {
1707    snapshot: Snapshot,
1708    include_ignored: bool,
1709    include_root_name: bool,
1710}
1711
1712impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
1713    type Candidates = CandidateSetIter<'a>;
1714
1715    fn id(&self) -> usize {
1716        self.snapshot.id().to_usize()
1717    }
1718
1719    fn len(&self) -> usize {
1720        if self.include_ignored {
1721            self.snapshot.file_count()
1722        } else {
1723            self.snapshot.visible_file_count()
1724        }
1725    }
1726
1727    fn prefix(&self) -> Arc<str> {
1728        if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
1729            self.snapshot.root_name().into()
1730        } else if self.include_root_name {
1731            format!("{}/", self.snapshot.root_name()).into()
1732        } else {
1733            "".into()
1734        }
1735    }
1736
1737    fn candidates(&'a self, start: usize) -> Self::Candidates {
1738        CandidateSetIter {
1739            traversal: self.snapshot.files(self.include_ignored, start),
1740        }
1741    }
1742}
1743
1744struct CandidateSetIter<'a> {
1745    traversal: Traversal<'a>,
1746}
1747
1748impl<'a> Iterator for CandidateSetIter<'a> {
1749    type Item = PathMatchCandidate<'a>;
1750
1751    fn next(&mut self) -> Option<Self::Item> {
1752        self.traversal.next().map(|entry| {
1753            if let EntryKind::File(char_bag) = entry.kind {
1754                PathMatchCandidate {
1755                    path: &entry.path,
1756                    char_bag,
1757                }
1758            } else {
1759                unreachable!()
1760            }
1761        })
1762    }
1763}
1764
1765impl Entity for Project {
1766    type Event = Event;
1767
1768    fn release(&mut self, cx: &mut gpui::MutableAppContext) {
1769        match &self.client_state {
1770            ProjectClientState::Local { remote_id_rx, .. } => {
1771                if let Some(project_id) = *remote_id_rx.borrow() {
1772                    let rpc = self.client.clone();
1773                    cx.spawn(|_| async move {
1774                        if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
1775                            log::error!("error unregistering project: {}", err);
1776                        }
1777                    })
1778                    .detach();
1779                }
1780            }
1781            ProjectClientState::Remote { remote_id, .. } => {
1782                let rpc = self.client.clone();
1783                let project_id = *remote_id;
1784                cx.spawn(|_| async move {
1785                    if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
1786                        log::error!("error leaving project: {}", err);
1787                    }
1788                })
1789                .detach();
1790            }
1791        }
1792    }
1793
1794    fn app_will_quit(
1795        &mut self,
1796        _: &mut MutableAppContext,
1797    ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
1798        use futures::FutureExt;
1799
1800        let shutdown_futures = self
1801            .language_servers
1802            .drain()
1803            .filter_map(|(_, server)| server.shutdown())
1804            .collect::<Vec<_>>();
1805        Some(
1806            async move {
1807                futures::future::join_all(shutdown_futures).await;
1808            }
1809            .boxed(),
1810        )
1811    }
1812}
1813
1814impl Collaborator {
1815    fn from_proto(
1816        message: proto::Collaborator,
1817        user_store: &ModelHandle<UserStore>,
1818        cx: &mut AsyncAppContext,
1819    ) -> impl Future<Output = Result<Self>> {
1820        let user = user_store.update(cx, |user_store, cx| {
1821            user_store.fetch_user(message.user_id, cx)
1822        });
1823
1824        async move {
1825            Ok(Self {
1826                peer_id: PeerId(message.peer_id),
1827                user: user.await?,
1828                replica_id: message.replica_id as ReplicaId,
1829            })
1830        }
1831    }
1832}
1833
1834impl<P: AsRef<Path>> From<(WorktreeId, P)> for ProjectPath {
1835    fn from((worktree_id, path): (WorktreeId, P)) -> Self {
1836        Self {
1837            worktree_id,
1838            path: path.as_ref().into(),
1839        }
1840    }
1841}
1842
1843impl OpenBuffer {
1844    fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
1845        match self {
1846            OpenBuffer::Loaded(buffer) => buffer.upgrade(cx),
1847            OpenBuffer::Operations(_) => None,
1848        }
1849    }
1850}
1851
1852#[cfg(test)]
1853mod tests {
1854    use super::{Event, *};
1855    use client::test::FakeHttpClient;
1856    use fs::RealFs;
1857    use futures::StreamExt;
1858    use gpui::{test::subscribe, TestAppContext};
1859    use language::{
1860        tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageRegistry,
1861        LanguageServerConfig, Point,
1862    };
1863    use lsp::Url;
1864    use serde_json::json;
1865    use std::{cell::RefCell, os::unix, path::PathBuf, rc::Rc};
1866    use unindent::Unindent as _;
1867    use util::test::temp_tree;
1868    use worktree::WorktreeHandle as _;
1869
1870    #[gpui::test]
1871    async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1872        let dir = temp_tree(json!({
1873            "root": {
1874                "apple": "",
1875                "banana": {
1876                    "carrot": {
1877                        "date": "",
1878                        "endive": "",
1879                    }
1880                },
1881                "fennel": {
1882                    "grape": "",
1883                }
1884            }
1885        }));
1886
1887        let root_link_path = dir.path().join("root_link");
1888        unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1889        unix::fs::symlink(
1890            &dir.path().join("root/fennel"),
1891            &dir.path().join("root/finnochio"),
1892        )
1893        .unwrap();
1894
1895        let project = build_project(Arc::new(RealFs), &mut cx);
1896
1897        let (tree, _) = project
1898            .update(&mut cx, |project, cx| {
1899                project.find_or_create_worktree_for_abs_path(&root_link_path, false, cx)
1900            })
1901            .await
1902            .unwrap();
1903
1904        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1905            .await;
1906        cx.read(|cx| {
1907            let tree = tree.read(cx);
1908            assert_eq!(tree.file_count(), 5);
1909            assert_eq!(
1910                tree.inode_for_path("fennel/grape"),
1911                tree.inode_for_path("finnochio/grape")
1912            );
1913        });
1914
1915        let cancel_flag = Default::default();
1916        let results = project
1917            .read_with(&cx, |project, cx| {
1918                project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1919            })
1920            .await;
1921        assert_eq!(
1922            results
1923                .into_iter()
1924                .map(|result| result.path)
1925                .collect::<Vec<Arc<Path>>>(),
1926            vec![
1927                PathBuf::from("banana/carrot/date").into(),
1928                PathBuf::from("banana/carrot/endive").into(),
1929            ]
1930        );
1931    }
1932
1933    #[gpui::test]
1934    async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
1935        let (language_server_config, mut fake_server) =
1936            LanguageServerConfig::fake(cx.background()).await;
1937        let progress_token = language_server_config
1938            .disk_based_diagnostics_progress_token
1939            .clone()
1940            .unwrap();
1941
1942        let mut languages = LanguageRegistry::new();
1943        languages.add(Arc::new(Language::new(
1944            LanguageConfig {
1945                name: "Rust".to_string(),
1946                path_suffixes: vec!["rs".to_string()],
1947                language_server: Some(language_server_config),
1948                ..Default::default()
1949            },
1950            Some(tree_sitter_rust::language()),
1951        )));
1952
1953        let dir = temp_tree(json!({
1954            "a.rs": "fn a() { A }",
1955            "b.rs": "const y: i32 = 1",
1956        }));
1957
1958        let http_client = FakeHttpClient::with_404_response();
1959        let client = Client::new(http_client.clone());
1960        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1961
1962        let project = cx.update(|cx| {
1963            Project::local(
1964                client,
1965                user_store,
1966                Arc::new(languages),
1967                Arc::new(RealFs),
1968                cx,
1969            )
1970        });
1971
1972        let (tree, _) = project
1973            .update(&mut cx, |project, cx| {
1974                project.find_or_create_worktree_for_abs_path(dir.path(), false, cx)
1975            })
1976            .await
1977            .unwrap();
1978        let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
1979
1980        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1981            .await;
1982
1983        // Cause worktree to start the fake language server
1984        let _buffer = project
1985            .update(&mut cx, |project, cx| {
1986                project.open_buffer(
1987                    ProjectPath {
1988                        worktree_id,
1989                        path: Path::new("b.rs").into(),
1990                    },
1991                    cx,
1992                )
1993            })
1994            .await
1995            .unwrap();
1996
1997        let mut events = subscribe(&project, &mut cx);
1998
1999        fake_server.start_progress(&progress_token).await;
2000        assert_eq!(
2001            events.next().await.unwrap(),
2002            Event::DiskBasedDiagnosticsStarted
2003        );
2004
2005        fake_server.start_progress(&progress_token).await;
2006        fake_server.end_progress(&progress_token).await;
2007        fake_server.start_progress(&progress_token).await;
2008
2009        fake_server
2010            .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
2011                uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
2012                version: None,
2013                diagnostics: vec![lsp::Diagnostic {
2014                    range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
2015                    severity: Some(lsp::DiagnosticSeverity::ERROR),
2016                    message: "undefined variable 'A'".to_string(),
2017                    ..Default::default()
2018                }],
2019            })
2020            .await;
2021        assert_eq!(
2022            events.next().await.unwrap(),
2023            Event::DiagnosticsUpdated(ProjectPath {
2024                worktree_id,
2025                path: Arc::from(Path::new("a.rs"))
2026            })
2027        );
2028
2029        fake_server.end_progress(&progress_token).await;
2030        fake_server.end_progress(&progress_token).await;
2031        assert_eq!(
2032            events.next().await.unwrap(),
2033            Event::DiskBasedDiagnosticsUpdated
2034        );
2035        assert_eq!(
2036            events.next().await.unwrap(),
2037            Event::DiskBasedDiagnosticsFinished
2038        );
2039
2040        let buffer = project
2041            .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
2042            .await
2043            .unwrap();
2044
2045        buffer.read_with(&cx, |buffer, _| {
2046            let snapshot = buffer.snapshot();
2047            let diagnostics = snapshot
2048                .diagnostics_in_range::<_, Point>(0..buffer.len())
2049                .collect::<Vec<_>>();
2050            assert_eq!(
2051                diagnostics,
2052                &[DiagnosticEntry {
2053                    range: Point::new(0, 9)..Point::new(0, 10),
2054                    diagnostic: Diagnostic {
2055                        severity: lsp::DiagnosticSeverity::ERROR,
2056                        message: "undefined variable 'A'".to_string(),
2057                        group_id: 0,
2058                        is_primary: true,
2059                        ..Default::default()
2060                    }
2061                }]
2062            )
2063        });
2064    }
2065
2066    #[gpui::test]
2067    async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
2068        let dir = temp_tree(json!({
2069            "root": {
2070                "dir1": {},
2071                "dir2": {
2072                    "dir3": {}
2073                }
2074            }
2075        }));
2076
2077        let project = build_project(Arc::new(RealFs), &mut cx);
2078        let (tree, _) = project
2079            .update(&mut cx, |project, cx| {
2080                project.find_or_create_worktree_for_abs_path(&dir.path(), false, cx)
2081            })
2082            .await
2083            .unwrap();
2084
2085        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2086            .await;
2087
2088        let cancel_flag = Default::default();
2089        let results = project
2090            .read_with(&cx, |project, cx| {
2091                project.match_paths("dir", false, false, 10, &cancel_flag, cx)
2092            })
2093            .await;
2094
2095        assert!(results.is_empty());
2096    }
2097
2098    #[gpui::test]
2099    async fn test_definition(mut cx: gpui::TestAppContext) {
2100        let (language_server_config, mut fake_server) =
2101            LanguageServerConfig::fake(cx.background()).await;
2102
2103        let mut languages = LanguageRegistry::new();
2104        languages.add(Arc::new(Language::new(
2105            LanguageConfig {
2106                name: "Rust".to_string(),
2107                path_suffixes: vec!["rs".to_string()],
2108                language_server: Some(language_server_config),
2109                ..Default::default()
2110            },
2111            Some(tree_sitter_rust::language()),
2112        )));
2113
2114        let dir = temp_tree(json!({
2115            "a.rs": "const fn a() { A }",
2116            "b.rs": "const y: i32 = crate::a()",
2117        }));
2118
2119        let http_client = FakeHttpClient::with_404_response();
2120        let client = Client::new(http_client.clone());
2121        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
2122        let project = cx.update(|cx| {
2123            Project::local(
2124                client,
2125                user_store,
2126                Arc::new(languages),
2127                Arc::new(RealFs),
2128                cx,
2129            )
2130        });
2131
2132        let (tree, _) = project
2133            .update(&mut cx, |project, cx| {
2134                project.find_or_create_worktree_for_abs_path(dir.path().join("b.rs"), false, cx)
2135            })
2136            .await
2137            .unwrap();
2138        let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
2139        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2140            .await;
2141
2142        // Cause worktree to start the fake language server
2143        let buffer = project
2144            .update(&mut cx, |project, cx| {
2145                project.open_buffer(
2146                    ProjectPath {
2147                        worktree_id,
2148                        path: Path::new("").into(),
2149                    },
2150                    cx,
2151                )
2152            })
2153            .await
2154            .unwrap();
2155        let definitions =
2156            project.update(&mut cx, |project, cx| project.definition(&buffer, 22, cx));
2157        let (request_id, request) = fake_server
2158            .receive_request::<lsp::request::GotoDefinition>()
2159            .await;
2160        let request_params = request.text_document_position_params;
2161        assert_eq!(
2162            request_params.text_document.uri.to_file_path().unwrap(),
2163            dir.path().join("b.rs")
2164        );
2165        assert_eq!(request_params.position, lsp::Position::new(0, 22));
2166
2167        fake_server
2168            .respond(
2169                request_id,
2170                Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
2171                    lsp::Url::from_file_path(dir.path().join("a.rs")).unwrap(),
2172                    lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
2173                ))),
2174            )
2175            .await;
2176        let mut definitions = definitions.await.unwrap();
2177        assert_eq!(definitions.len(), 1);
2178        let definition = definitions.pop().unwrap();
2179        cx.update(|cx| {
2180            let target_buffer = definition.target_buffer.read(cx);
2181            assert_eq!(
2182                target_buffer.file().unwrap().abs_path(),
2183                Some(dir.path().join("a.rs"))
2184            );
2185            assert_eq!(definition.target_range.to_offset(target_buffer), 9..10);
2186            assert_eq!(
2187                list_worktrees(&project, cx),
2188                [
2189                    (dir.path().join("b.rs"), false),
2190                    (dir.path().join("a.rs"), true)
2191                ]
2192            );
2193
2194            drop(definition);
2195        });
2196        cx.read(|cx| {
2197            assert_eq!(
2198                list_worktrees(&project, cx),
2199                [(dir.path().join("b.rs"), false)]
2200            );
2201        });
2202
2203        fn list_worktrees(project: &ModelHandle<Project>, cx: &AppContext) -> Vec<(PathBuf, bool)> {
2204            project
2205                .read(cx)
2206                .worktrees(cx)
2207                .map(|worktree| {
2208                    let worktree = worktree.read(cx);
2209                    (
2210                        worktree.as_local().unwrap().abs_path().to_path_buf(),
2211                        worktree.is_weak(),
2212                    )
2213                })
2214                .collect::<Vec<_>>()
2215        }
2216    }
2217
2218    #[gpui::test]
2219    async fn test_save_file(mut cx: gpui::TestAppContext) {
2220        let fs = Arc::new(FakeFs::new());
2221        fs.insert_tree(
2222            "/dir",
2223            json!({
2224                "file1": "the old contents",
2225            }),
2226        )
2227        .await;
2228
2229        let project = build_project(fs.clone(), &mut cx);
2230        let worktree_id = project
2231            .update(&mut cx, |p, cx| p.add_local_worktree("/dir", false, cx))
2232            .await
2233            .unwrap()
2234            .read_with(&cx, |tree, _| tree.id());
2235
2236        let buffer = project
2237            .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
2238            .await
2239            .unwrap();
2240        buffer
2241            .update(&mut cx, |buffer, cx| {
2242                assert_eq!(buffer.text(), "the old contents");
2243                buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
2244                buffer.save(cx)
2245            })
2246            .await
2247            .unwrap();
2248
2249        let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2250        assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
2251    }
2252
2253    #[gpui::test]
2254    async fn test_save_in_single_file_worktree(mut cx: gpui::TestAppContext) {
2255        let fs = Arc::new(FakeFs::new());
2256        fs.insert_tree(
2257            "/dir",
2258            json!({
2259                "file1": "the old contents",
2260            }),
2261        )
2262        .await;
2263
2264        let project = build_project(fs.clone(), &mut cx);
2265        let worktree_id = project
2266            .update(&mut cx, |p, cx| {
2267                p.add_local_worktree("/dir/file1", false, cx)
2268            })
2269            .await
2270            .unwrap()
2271            .read_with(&cx, |tree, _| tree.id());
2272
2273        let buffer = project
2274            .update(&mut cx, |p, cx| p.open_buffer((worktree_id, ""), cx))
2275            .await
2276            .unwrap();
2277        buffer
2278            .update(&mut cx, |buffer, cx| {
2279                buffer.edit(Some(0..0), "a line of text.\n".repeat(10 * 1024), cx);
2280                buffer.save(cx)
2281            })
2282            .await
2283            .unwrap();
2284
2285        let new_text = fs.load(Path::new("/dir/file1")).await.unwrap();
2286        assert_eq!(new_text, buffer.read_with(&cx, |buffer, _| buffer.text()));
2287    }
2288
2289    #[gpui::test]
2290    async fn test_rescan_and_remote_updates(mut cx: gpui::TestAppContext) {
2291        let dir = temp_tree(json!({
2292            "a": {
2293                "file1": "",
2294                "file2": "",
2295                "file3": "",
2296            },
2297            "b": {
2298                "c": {
2299                    "file4": "",
2300                    "file5": "",
2301                }
2302            }
2303        }));
2304
2305        let project = build_project(Arc::new(RealFs), &mut cx);
2306        let rpc = project.read_with(&cx, |p, _| p.client.clone());
2307
2308        let tree = project
2309            .update(&mut cx, |p, cx| p.add_local_worktree(dir.path(), false, cx))
2310            .await
2311            .unwrap();
2312        let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
2313
2314        let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| {
2315            let buffer = project.update(cx, |p, cx| p.open_buffer((worktree_id, path), cx));
2316            async move { buffer.await.unwrap() }
2317        };
2318        let id_for_path = |path: &'static str, cx: &gpui::TestAppContext| {
2319            tree.read_with(cx, |tree, _| {
2320                tree.entry_for_path(path)
2321                    .expect(&format!("no entry for path {}", path))
2322                    .id
2323            })
2324        };
2325
2326        let buffer2 = buffer_for_path("a/file2", &mut cx).await;
2327        let buffer3 = buffer_for_path("a/file3", &mut cx).await;
2328        let buffer4 = buffer_for_path("b/c/file4", &mut cx).await;
2329        let buffer5 = buffer_for_path("b/c/file5", &mut cx).await;
2330
2331        let file2_id = id_for_path("a/file2", &cx);
2332        let file3_id = id_for_path("a/file3", &cx);
2333        let file4_id = id_for_path("b/c/file4", &cx);
2334
2335        // Wait for the initial scan.
2336        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
2337            .await;
2338
2339        // Create a remote copy of this worktree.
2340        let initial_snapshot = tree.read_with(&cx, |tree, _| tree.snapshot());
2341        let remote = Worktree::remote(
2342            1,
2343            1,
2344            initial_snapshot.to_proto(&Default::default(), Default::default()),
2345            rpc.clone(),
2346            &mut cx.to_async(),
2347        )
2348        .await
2349        .unwrap();
2350
2351        cx.read(|cx| {
2352            assert!(!buffer2.read(cx).is_dirty());
2353            assert!(!buffer3.read(cx).is_dirty());
2354            assert!(!buffer4.read(cx).is_dirty());
2355            assert!(!buffer5.read(cx).is_dirty());
2356        });
2357
2358        // Rename and delete files and directories.
2359        tree.flush_fs_events(&cx).await;
2360        std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
2361        std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
2362        std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
2363        std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
2364        tree.flush_fs_events(&cx).await;
2365
2366        let expected_paths = vec![
2367            "a",
2368            "a/file1",
2369            "a/file2.new",
2370            "b",
2371            "d",
2372            "d/file3",
2373            "d/file4",
2374        ];
2375
2376        cx.read(|app| {
2377            assert_eq!(
2378                tree.read(app)
2379                    .paths()
2380                    .map(|p| p.to_str().unwrap())
2381                    .collect::<Vec<_>>(),
2382                expected_paths
2383            );
2384
2385            assert_eq!(id_for_path("a/file2.new", &cx), file2_id);
2386            assert_eq!(id_for_path("d/file3", &cx), file3_id);
2387            assert_eq!(id_for_path("d/file4", &cx), file4_id);
2388
2389            assert_eq!(
2390                buffer2.read(app).file().unwrap().path().as_ref(),
2391                Path::new("a/file2.new")
2392            );
2393            assert_eq!(
2394                buffer3.read(app).file().unwrap().path().as_ref(),
2395                Path::new("d/file3")
2396            );
2397            assert_eq!(
2398                buffer4.read(app).file().unwrap().path().as_ref(),
2399                Path::new("d/file4")
2400            );
2401            assert_eq!(
2402                buffer5.read(app).file().unwrap().path().as_ref(),
2403                Path::new("b/c/file5")
2404            );
2405
2406            assert!(!buffer2.read(app).file().unwrap().is_deleted());
2407            assert!(!buffer3.read(app).file().unwrap().is_deleted());
2408            assert!(!buffer4.read(app).file().unwrap().is_deleted());
2409            assert!(buffer5.read(app).file().unwrap().is_deleted());
2410        });
2411
2412        // Update the remote worktree. Check that it becomes consistent with the
2413        // local worktree.
2414        remote.update(&mut cx, |remote, cx| {
2415            let update_message =
2416                tree.read(cx)
2417                    .snapshot()
2418                    .build_update(&initial_snapshot, 1, 1, true);
2419            remote
2420                .as_remote_mut()
2421                .unwrap()
2422                .snapshot
2423                .apply_update(update_message)
2424                .unwrap();
2425
2426            assert_eq!(
2427                remote
2428                    .paths()
2429                    .map(|p| p.to_str().unwrap())
2430                    .collect::<Vec<_>>(),
2431                expected_paths
2432            );
2433        });
2434    }
2435
2436    #[gpui::test]
2437    async fn test_buffer_deduping(mut cx: gpui::TestAppContext) {
2438        let fs = Arc::new(FakeFs::new());
2439        fs.insert_tree(
2440            "/the-dir",
2441            json!({
2442                "a.txt": "a-contents",
2443                "b.txt": "b-contents",
2444            }),
2445        )
2446        .await;
2447
2448        let project = build_project(fs.clone(), &mut cx);
2449        let worktree_id = project
2450            .update(&mut cx, |p, cx| p.add_local_worktree("/the-dir", false, cx))
2451            .await
2452            .unwrap()
2453            .read_with(&cx, |tree, _| tree.id());
2454
2455        // Spawn multiple tasks to open paths, repeating some paths.
2456        let (buffer_a_1, buffer_b, buffer_a_2) = project.update(&mut cx, |p, cx| {
2457            (
2458                p.open_buffer((worktree_id, "a.txt"), cx),
2459                p.open_buffer((worktree_id, "b.txt"), cx),
2460                p.open_buffer((worktree_id, "a.txt"), cx),
2461            )
2462        });
2463
2464        let buffer_a_1 = buffer_a_1.await.unwrap();
2465        let buffer_a_2 = buffer_a_2.await.unwrap();
2466        let buffer_b = buffer_b.await.unwrap();
2467        assert_eq!(buffer_a_1.read_with(&cx, |b, _| b.text()), "a-contents");
2468        assert_eq!(buffer_b.read_with(&cx, |b, _| b.text()), "b-contents");
2469
2470        // There is only one buffer per path.
2471        let buffer_a_id = buffer_a_1.id();
2472        assert_eq!(buffer_a_2.id(), buffer_a_id);
2473
2474        // Open the same path again while it is still open.
2475        drop(buffer_a_1);
2476        let buffer_a_3 = project
2477            .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2478            .await
2479            .unwrap();
2480
2481        // There's still only one buffer per path.
2482        assert_eq!(buffer_a_3.id(), buffer_a_id);
2483    }
2484
2485    #[gpui::test]
2486    async fn test_buffer_is_dirty(mut cx: gpui::TestAppContext) {
2487        use std::fs;
2488
2489        let dir = temp_tree(json!({
2490            "file1": "abc",
2491            "file2": "def",
2492            "file3": "ghi",
2493        }));
2494
2495        let project = build_project(Arc::new(RealFs), &mut cx);
2496        let worktree = project
2497            .update(&mut cx, |p, cx| p.add_local_worktree(dir.path(), false, cx))
2498            .await
2499            .unwrap();
2500        let worktree_id = worktree.read_with(&cx, |worktree, _| worktree.id());
2501
2502        worktree.flush_fs_events(&cx).await;
2503        worktree
2504            .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
2505            .await;
2506
2507        let buffer1 = project
2508            .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
2509            .await
2510            .unwrap();
2511        let events = Rc::new(RefCell::new(Vec::new()));
2512
2513        // initially, the buffer isn't dirty.
2514        buffer1.update(&mut cx, |buffer, cx| {
2515            cx.subscribe(&buffer1, {
2516                let events = events.clone();
2517                move |_, _, event, _| events.borrow_mut().push(event.clone())
2518            })
2519            .detach();
2520
2521            assert!(!buffer.is_dirty());
2522            assert!(events.borrow().is_empty());
2523
2524            buffer.edit(vec![1..2], "", cx);
2525        });
2526
2527        // after the first edit, the buffer is dirty, and emits a dirtied event.
2528        buffer1.update(&mut cx, |buffer, cx| {
2529            assert!(buffer.text() == "ac");
2530            assert!(buffer.is_dirty());
2531            assert_eq!(
2532                *events.borrow(),
2533                &[language::Event::Edited, language::Event::Dirtied]
2534            );
2535            events.borrow_mut().clear();
2536            buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
2537        });
2538
2539        // after saving, the buffer is not dirty, and emits a saved event.
2540        buffer1.update(&mut cx, |buffer, cx| {
2541            assert!(!buffer.is_dirty());
2542            assert_eq!(*events.borrow(), &[language::Event::Saved]);
2543            events.borrow_mut().clear();
2544
2545            buffer.edit(vec![1..1], "B", cx);
2546            buffer.edit(vec![2..2], "D", cx);
2547        });
2548
2549        // after editing again, the buffer is dirty, and emits another dirty event.
2550        buffer1.update(&mut cx, |buffer, cx| {
2551            assert!(buffer.text() == "aBDc");
2552            assert!(buffer.is_dirty());
2553            assert_eq!(
2554                *events.borrow(),
2555                &[
2556                    language::Event::Edited,
2557                    language::Event::Dirtied,
2558                    language::Event::Edited,
2559                ],
2560            );
2561            events.borrow_mut().clear();
2562
2563            // TODO - currently, after restoring the buffer to its
2564            // previously-saved state, the is still considered dirty.
2565            buffer.edit([1..3], "", cx);
2566            assert!(buffer.text() == "ac");
2567            assert!(buffer.is_dirty());
2568        });
2569
2570        assert_eq!(*events.borrow(), &[language::Event::Edited]);
2571
2572        // When a file is deleted, the buffer is considered dirty.
2573        let events = Rc::new(RefCell::new(Vec::new()));
2574        let buffer2 = project
2575            .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file2"), cx))
2576            .await
2577            .unwrap();
2578        buffer2.update(&mut cx, |_, cx| {
2579            cx.subscribe(&buffer2, {
2580                let events = events.clone();
2581                move |_, _, event, _| events.borrow_mut().push(event.clone())
2582            })
2583            .detach();
2584        });
2585
2586        fs::remove_file(dir.path().join("file2")).unwrap();
2587        buffer2.condition(&cx, |b, _| b.is_dirty()).await;
2588        assert_eq!(
2589            *events.borrow(),
2590            &[language::Event::Dirtied, language::Event::FileHandleChanged]
2591        );
2592
2593        // When a file is already dirty when deleted, we don't emit a Dirtied event.
2594        let events = Rc::new(RefCell::new(Vec::new()));
2595        let buffer3 = project
2596            .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "file3"), cx))
2597            .await
2598            .unwrap();
2599        buffer3.update(&mut cx, |_, cx| {
2600            cx.subscribe(&buffer3, {
2601                let events = events.clone();
2602                move |_, _, event, _| events.borrow_mut().push(event.clone())
2603            })
2604            .detach();
2605        });
2606
2607        worktree.flush_fs_events(&cx).await;
2608        buffer3.update(&mut cx, |buffer, cx| {
2609            buffer.edit(Some(0..0), "x", cx);
2610        });
2611        events.borrow_mut().clear();
2612        fs::remove_file(dir.path().join("file3")).unwrap();
2613        buffer3
2614            .condition(&cx, |_, _| !events.borrow().is_empty())
2615            .await;
2616        assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
2617        cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
2618    }
2619
2620    #[gpui::test]
2621    async fn test_buffer_file_changes_on_disk(mut cx: gpui::TestAppContext) {
2622        use std::fs;
2623
2624        let initial_contents = "aaa\nbbbbb\nc\n";
2625        let dir = temp_tree(json!({ "the-file": initial_contents }));
2626
2627        let project = build_project(Arc::new(RealFs), &mut cx);
2628        let worktree = project
2629            .update(&mut cx, |p, cx| p.add_local_worktree(dir.path(), false, cx))
2630            .await
2631            .unwrap();
2632        let worktree_id = worktree.read_with(&cx, |tree, _| tree.id());
2633
2634        worktree
2635            .read_with(&cx, |t, _| t.as_local().unwrap().scan_complete())
2636            .await;
2637
2638        let abs_path = dir.path().join("the-file");
2639        let buffer = project
2640            .update(&mut cx, |p, cx| {
2641                p.open_buffer((worktree_id, "the-file"), cx)
2642            })
2643            .await
2644            .unwrap();
2645
2646        // TODO
2647        // Add a cursor on each row.
2648        // let selection_set_id = buffer.update(&mut cx, |buffer, cx| {
2649        //     assert!(!buffer.is_dirty());
2650        //     buffer.add_selection_set(
2651        //         &(0..3)
2652        //             .map(|row| Selection {
2653        //                 id: row as usize,
2654        //                 start: Point::new(row, 1),
2655        //                 end: Point::new(row, 1),
2656        //                 reversed: false,
2657        //                 goal: SelectionGoal::None,
2658        //             })
2659        //             .collect::<Vec<_>>(),
2660        //         cx,
2661        //     )
2662        // });
2663
2664        // Change the file on disk, adding two new lines of text, and removing
2665        // one line.
2666        buffer.read_with(&cx, |buffer, _| {
2667            assert!(!buffer.is_dirty());
2668            assert!(!buffer.has_conflict());
2669        });
2670        let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
2671        fs::write(&abs_path, new_contents).unwrap();
2672
2673        // Because the buffer was not modified, it is reloaded from disk. Its
2674        // contents are edited according to the diff between the old and new
2675        // file contents.
2676        buffer
2677            .condition(&cx, |buffer, _| buffer.text() == new_contents)
2678            .await;
2679
2680        buffer.update(&mut cx, |buffer, _| {
2681            assert_eq!(buffer.text(), new_contents);
2682            assert!(!buffer.is_dirty());
2683            assert!(!buffer.has_conflict());
2684
2685            // TODO
2686            // let cursor_positions = buffer
2687            //     .selection_set(selection_set_id)
2688            //     .unwrap()
2689            //     .selections::<Point>(&*buffer)
2690            //     .map(|selection| {
2691            //         assert_eq!(selection.start, selection.end);
2692            //         selection.start
2693            //     })
2694            //     .collect::<Vec<_>>();
2695            // assert_eq!(
2696            //     cursor_positions,
2697            //     [Point::new(1, 1), Point::new(3, 1), Point::new(4, 0)]
2698            // );
2699        });
2700
2701        // Modify the buffer
2702        buffer.update(&mut cx, |buffer, cx| {
2703            buffer.edit(vec![0..0], " ", cx);
2704            assert!(buffer.is_dirty());
2705            assert!(!buffer.has_conflict());
2706        });
2707
2708        // Change the file on disk again, adding blank lines to the beginning.
2709        fs::write(&abs_path, "\n\n\nAAAA\naaa\nBB\nbbbbb\n").unwrap();
2710
2711        // Because the buffer is modified, it doesn't reload from disk, but is
2712        // marked as having a conflict.
2713        buffer
2714            .condition(&cx, |buffer, _| buffer.has_conflict())
2715            .await;
2716    }
2717
2718    #[gpui::test]
2719    async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) {
2720        let fs = Arc::new(FakeFs::new());
2721        fs.insert_tree(
2722            "/the-dir",
2723            json!({
2724                "a.rs": "
2725                    fn foo(mut v: Vec<usize>) {
2726                        for x in &v {
2727                            v.push(1);
2728                        }
2729                    }
2730                "
2731                .unindent(),
2732            }),
2733        )
2734        .await;
2735
2736        let project = build_project(fs.clone(), &mut cx);
2737        let worktree = project
2738            .update(&mut cx, |p, cx| p.add_local_worktree("/the-dir", false, cx))
2739            .await
2740            .unwrap();
2741        let worktree_id = worktree.read_with(&cx, |tree, _| tree.id());
2742
2743        let buffer = project
2744            .update(&mut cx, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
2745            .await
2746            .unwrap();
2747
2748        let buffer_uri = Url::from_file_path("/the-dir/a.rs").unwrap();
2749        let message = lsp::PublishDiagnosticsParams {
2750            uri: buffer_uri.clone(),
2751            diagnostics: vec![
2752                lsp::Diagnostic {
2753                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2754                    severity: Some(DiagnosticSeverity::WARNING),
2755                    message: "error 1".to_string(),
2756                    related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2757                        location: lsp::Location {
2758                            uri: buffer_uri.clone(),
2759                            range: lsp::Range::new(
2760                                lsp::Position::new(1, 8),
2761                                lsp::Position::new(1, 9),
2762                            ),
2763                        },
2764                        message: "error 1 hint 1".to_string(),
2765                    }]),
2766                    ..Default::default()
2767                },
2768                lsp::Diagnostic {
2769                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)),
2770                    severity: Some(DiagnosticSeverity::HINT),
2771                    message: "error 1 hint 1".to_string(),
2772                    related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2773                        location: lsp::Location {
2774                            uri: buffer_uri.clone(),
2775                            range: lsp::Range::new(
2776                                lsp::Position::new(1, 8),
2777                                lsp::Position::new(1, 9),
2778                            ),
2779                        },
2780                        message: "original diagnostic".to_string(),
2781                    }]),
2782                    ..Default::default()
2783                },
2784                lsp::Diagnostic {
2785                    range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)),
2786                    severity: Some(DiagnosticSeverity::ERROR),
2787                    message: "error 2".to_string(),
2788                    related_information: Some(vec![
2789                        lsp::DiagnosticRelatedInformation {
2790                            location: lsp::Location {
2791                                uri: buffer_uri.clone(),
2792                                range: lsp::Range::new(
2793                                    lsp::Position::new(1, 13),
2794                                    lsp::Position::new(1, 15),
2795                                ),
2796                            },
2797                            message: "error 2 hint 1".to_string(),
2798                        },
2799                        lsp::DiagnosticRelatedInformation {
2800                            location: lsp::Location {
2801                                uri: buffer_uri.clone(),
2802                                range: lsp::Range::new(
2803                                    lsp::Position::new(1, 13),
2804                                    lsp::Position::new(1, 15),
2805                                ),
2806                            },
2807                            message: "error 2 hint 2".to_string(),
2808                        },
2809                    ]),
2810                    ..Default::default()
2811                },
2812                lsp::Diagnostic {
2813                    range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
2814                    severity: Some(DiagnosticSeverity::HINT),
2815                    message: "error 2 hint 1".to_string(),
2816                    related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2817                        location: lsp::Location {
2818                            uri: buffer_uri.clone(),
2819                            range: lsp::Range::new(
2820                                lsp::Position::new(2, 8),
2821                                lsp::Position::new(2, 17),
2822                            ),
2823                        },
2824                        message: "original diagnostic".to_string(),
2825                    }]),
2826                    ..Default::default()
2827                },
2828                lsp::Diagnostic {
2829                    range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)),
2830                    severity: Some(DiagnosticSeverity::HINT),
2831                    message: "error 2 hint 2".to_string(),
2832                    related_information: Some(vec![lsp::DiagnosticRelatedInformation {
2833                        location: lsp::Location {
2834                            uri: buffer_uri.clone(),
2835                            range: lsp::Range::new(
2836                                lsp::Position::new(2, 8),
2837                                lsp::Position::new(2, 17),
2838                            ),
2839                        },
2840                        message: "original diagnostic".to_string(),
2841                    }]),
2842                    ..Default::default()
2843                },
2844            ],
2845            version: None,
2846        };
2847
2848        project
2849            .update(&mut cx, |p, cx| {
2850                p.update_diagnostics(message, &Default::default(), cx)
2851            })
2852            .unwrap();
2853        let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot());
2854
2855        assert_eq!(
2856            buffer
2857                .diagnostics_in_range::<_, Point>(0..buffer.len())
2858                .collect::<Vec<_>>(),
2859            &[
2860                DiagnosticEntry {
2861                    range: Point::new(1, 8)..Point::new(1, 9),
2862                    diagnostic: Diagnostic {
2863                        severity: DiagnosticSeverity::WARNING,
2864                        message: "error 1".to_string(),
2865                        group_id: 0,
2866                        is_primary: true,
2867                        ..Default::default()
2868                    }
2869                },
2870                DiagnosticEntry {
2871                    range: Point::new(1, 8)..Point::new(1, 9),
2872                    diagnostic: Diagnostic {
2873                        severity: DiagnosticSeverity::HINT,
2874                        message: "error 1 hint 1".to_string(),
2875                        group_id: 0,
2876                        is_primary: false,
2877                        ..Default::default()
2878                    }
2879                },
2880                DiagnosticEntry {
2881                    range: Point::new(1, 13)..Point::new(1, 15),
2882                    diagnostic: Diagnostic {
2883                        severity: DiagnosticSeverity::HINT,
2884                        message: "error 2 hint 1".to_string(),
2885                        group_id: 1,
2886                        is_primary: false,
2887                        ..Default::default()
2888                    }
2889                },
2890                DiagnosticEntry {
2891                    range: Point::new(1, 13)..Point::new(1, 15),
2892                    diagnostic: Diagnostic {
2893                        severity: DiagnosticSeverity::HINT,
2894                        message: "error 2 hint 2".to_string(),
2895                        group_id: 1,
2896                        is_primary: false,
2897                        ..Default::default()
2898                    }
2899                },
2900                DiagnosticEntry {
2901                    range: Point::new(2, 8)..Point::new(2, 17),
2902                    diagnostic: Diagnostic {
2903                        severity: DiagnosticSeverity::ERROR,
2904                        message: "error 2".to_string(),
2905                        group_id: 1,
2906                        is_primary: true,
2907                        ..Default::default()
2908                    }
2909                }
2910            ]
2911        );
2912
2913        assert_eq!(
2914            buffer.diagnostic_group::<Point>(0).collect::<Vec<_>>(),
2915            &[
2916                DiagnosticEntry {
2917                    range: Point::new(1, 8)..Point::new(1, 9),
2918                    diagnostic: Diagnostic {
2919                        severity: DiagnosticSeverity::WARNING,
2920                        message: "error 1".to_string(),
2921                        group_id: 0,
2922                        is_primary: true,
2923                        ..Default::default()
2924                    }
2925                },
2926                DiagnosticEntry {
2927                    range: Point::new(1, 8)..Point::new(1, 9),
2928                    diagnostic: Diagnostic {
2929                        severity: DiagnosticSeverity::HINT,
2930                        message: "error 1 hint 1".to_string(),
2931                        group_id: 0,
2932                        is_primary: false,
2933                        ..Default::default()
2934                    }
2935                },
2936            ]
2937        );
2938        assert_eq!(
2939            buffer.diagnostic_group::<Point>(1).collect::<Vec<_>>(),
2940            &[
2941                DiagnosticEntry {
2942                    range: Point::new(1, 13)..Point::new(1, 15),
2943                    diagnostic: Diagnostic {
2944                        severity: DiagnosticSeverity::HINT,
2945                        message: "error 2 hint 1".to_string(),
2946                        group_id: 1,
2947                        is_primary: false,
2948                        ..Default::default()
2949                    }
2950                },
2951                DiagnosticEntry {
2952                    range: Point::new(1, 13)..Point::new(1, 15),
2953                    diagnostic: Diagnostic {
2954                        severity: DiagnosticSeverity::HINT,
2955                        message: "error 2 hint 2".to_string(),
2956                        group_id: 1,
2957                        is_primary: false,
2958                        ..Default::default()
2959                    }
2960                },
2961                DiagnosticEntry {
2962                    range: Point::new(2, 8)..Point::new(2, 17),
2963                    diagnostic: Diagnostic {
2964                        severity: DiagnosticSeverity::ERROR,
2965                        message: "error 2".to_string(),
2966                        group_id: 1,
2967                        is_primary: true,
2968                        ..Default::default()
2969                    }
2970                }
2971            ]
2972        );
2973    }
2974
2975    fn build_project(fs: Arc<dyn Fs>, cx: &mut TestAppContext) -> ModelHandle<Project> {
2976        let languages = Arc::new(LanguageRegistry::new());
2977        let http_client = FakeHttpClient::with_404_response();
2978        let client = client::Client::new(http_client.clone());
2979        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
2980        cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
2981    }
2982}