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