1pub mod fs;
2mod ignore;
3pub mod worktree;
4
5use anyhow::{anyhow, Result};
6use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
7use clock::ReplicaId;
8use collections::HashMap;
9use futures::Future;
10use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
11use gpui::{
12 AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
13};
14use language::{Buffer, DiagnosticEntry, LanguageRegistry};
15use lsp::DiagnosticSeverity;
16use postage::{prelude::Stream, watch};
17use std::{
18 path::Path,
19 sync::{atomic::AtomicBool, Arc},
20};
21use util::TryFutureExt as _;
22
23pub use fs::*;
24pub use worktree::*;
25
26pub struct Project {
27 worktrees: Vec<ModelHandle<Worktree>>,
28 active_entry: Option<ProjectEntry>,
29 languages: Arc<LanguageRegistry>,
30 client: Arc<client::Client>,
31 user_store: ModelHandle<UserStore>,
32 fs: Arc<dyn Fs>,
33 client_state: ProjectClientState,
34 collaborators: HashMap<PeerId, Collaborator>,
35 subscriptions: Vec<client::Subscription>,
36 pending_disk_based_diagnostics: isize,
37}
38
39enum ProjectClientState {
40 Local {
41 is_shared: bool,
42 remote_id_tx: watch::Sender<Option<u64>>,
43 remote_id_rx: watch::Receiver<Option<u64>>,
44 _maintain_remote_id_task: Task<Option<()>>,
45 },
46 Remote {
47 sharing_has_stopped: bool,
48 remote_id: u64,
49 replica_id: ReplicaId,
50 },
51}
52
53#[derive(Clone, Debug)]
54pub struct Collaborator {
55 pub user: Arc<User>,
56 pub peer_id: PeerId,
57 pub replica_id: ReplicaId,
58}
59
60#[derive(Debug)]
61pub enum Event {
62 ActiveEntryChanged(Option<ProjectEntry>),
63 WorktreeRemoved(WorktreeId),
64 DiskBasedDiagnosticsStarted,
65 DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId },
66 DiskBasedDiagnosticsFinished,
67 DiagnosticsUpdated(ProjectPath),
68}
69
70#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
71pub struct ProjectPath {
72 pub worktree_id: WorktreeId,
73 pub path: Arc<Path>,
74}
75
76#[derive(Clone, Debug, Default, PartialEq)]
77pub struct DiagnosticSummary {
78 pub error_count: usize,
79 pub warning_count: usize,
80 pub info_count: usize,
81 pub hint_count: usize,
82}
83
84impl DiagnosticSummary {
85 fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
86 let mut this = Self {
87 error_count: 0,
88 warning_count: 0,
89 info_count: 0,
90 hint_count: 0,
91 };
92
93 for entry in diagnostics {
94 if entry.diagnostic.is_primary {
95 match entry.diagnostic.severity {
96 DiagnosticSeverity::ERROR => this.error_count += 1,
97 DiagnosticSeverity::WARNING => this.warning_count += 1,
98 DiagnosticSeverity::INFORMATION => this.info_count += 1,
99 DiagnosticSeverity::HINT => this.hint_count += 1,
100 _ => {}
101 }
102 }
103 }
104
105 this
106 }
107
108 pub fn to_proto(&self, path: Arc<Path>) -> proto::DiagnosticSummary {
109 proto::DiagnosticSummary {
110 path: path.to_string_lossy().to_string(),
111 error_count: self.error_count as u32,
112 warning_count: self.warning_count as u32,
113 info_count: self.info_count as u32,
114 hint_count: self.hint_count as u32,
115 }
116 }
117}
118
119#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
120pub struct ProjectEntry {
121 pub worktree_id: WorktreeId,
122 pub entry_id: usize,
123}
124
125impl Project {
126 pub fn local(
127 client: Arc<Client>,
128 user_store: ModelHandle<UserStore>,
129 languages: Arc<LanguageRegistry>,
130 fs: Arc<dyn Fs>,
131 cx: &mut MutableAppContext,
132 ) -> ModelHandle<Self> {
133 cx.add_model(|cx: &mut ModelContext<Self>| {
134 let (remote_id_tx, remote_id_rx) = watch::channel();
135 let _maintain_remote_id_task = cx.spawn_weak({
136 let rpc = client.clone();
137 move |this, mut cx| {
138 async move {
139 let mut status = rpc.status();
140 while let Some(status) = status.recv().await {
141 if let Some(this) = this.upgrade(&cx) {
142 let remote_id = if let client::Status::Connected { .. } = status {
143 let response = rpc.request(proto::RegisterProject {}).await?;
144 Some(response.project_id)
145 } else {
146 None
147 };
148
149 if let Some(project_id) = remote_id {
150 let mut registrations = Vec::new();
151 this.read_with(&cx, |this, cx| {
152 for worktree in &this.worktrees {
153 let worktree_id = worktree.id() as u64;
154 let worktree = worktree.read(cx).as_local().unwrap();
155 registrations.push(rpc.request(
156 proto::RegisterWorktree {
157 project_id,
158 worktree_id,
159 root_name: worktree.root_name().to_string(),
160 authorized_logins: worktree.authorized_logins(),
161 },
162 ));
163 }
164 });
165 for registration in registrations {
166 registration.await?;
167 }
168 }
169 this.update(&mut cx, |this, cx| this.set_remote_id(remote_id, cx));
170 }
171 }
172 Ok(())
173 }
174 .log_err()
175 }
176 });
177
178 Self {
179 worktrees: Default::default(),
180 collaborators: Default::default(),
181 client_state: ProjectClientState::Local {
182 is_shared: false,
183 remote_id_tx,
184 remote_id_rx,
185 _maintain_remote_id_task,
186 },
187 subscriptions: Vec::new(),
188 active_entry: None,
189 languages,
190 client,
191 user_store,
192 fs,
193 pending_disk_based_diagnostics: 0,
194 }
195 })
196 }
197
198 pub async fn remote(
199 remote_id: u64,
200 client: Arc<Client>,
201 user_store: ModelHandle<UserStore>,
202 languages: Arc<LanguageRegistry>,
203 fs: Arc<dyn Fs>,
204 cx: &mut AsyncAppContext,
205 ) -> Result<ModelHandle<Self>> {
206 client.authenticate_and_connect(&cx).await?;
207
208 let response = client
209 .request(proto::JoinProject {
210 project_id: remote_id,
211 })
212 .await?;
213
214 let replica_id = response.replica_id as ReplicaId;
215
216 let mut worktrees = Vec::new();
217 for worktree in response.worktrees {
218 worktrees.push(
219 Worktree::remote(
220 remote_id,
221 replica_id,
222 worktree,
223 client.clone(),
224 user_store.clone(),
225 languages.clone(),
226 cx,
227 )
228 .await?,
229 );
230 }
231
232 let user_ids = response
233 .collaborators
234 .iter()
235 .map(|peer| peer.user_id)
236 .collect();
237 user_store
238 .update(cx, |user_store, cx| user_store.load_users(user_ids, cx))
239 .await?;
240 let mut collaborators = HashMap::default();
241 for message in response.collaborators {
242 let collaborator = Collaborator::from_proto(message, &user_store, cx).await?;
243 collaborators.insert(collaborator.peer_id, collaborator);
244 }
245
246 Ok(cx.add_model(|cx| {
247 let mut this = Self {
248 worktrees: Vec::new(),
249 active_entry: None,
250 collaborators,
251 languages,
252 user_store,
253 fs,
254 subscriptions: vec![
255 client.subscribe_to_entity(remote_id, cx, Self::handle_unshare_project),
256 client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
257 client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
258 client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree),
259 client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree),
260 client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
261 client.subscribe_to_entity(
262 remote_id,
263 cx,
264 Self::handle_update_diagnostic_summary,
265 ),
266 client.subscribe_to_entity(
267 remote_id,
268 cx,
269 Self::handle_disk_based_diagnostics_updating,
270 ),
271 client.subscribe_to_entity(
272 remote_id,
273 cx,
274 Self::handle_disk_based_diagnostics_updated,
275 ),
276 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
277 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
278 ],
279 client,
280 client_state: ProjectClientState::Remote {
281 sharing_has_stopped: false,
282 remote_id,
283 replica_id,
284 },
285 pending_disk_based_diagnostics: 0,
286 };
287 for worktree in worktrees {
288 this.add_worktree(worktree, cx);
289 }
290 this
291 }))
292 }
293
294 fn set_remote_id(&mut self, remote_id: Option<u64>, cx: &mut ModelContext<Self>) {
295 if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
296 *remote_id_tx.borrow_mut() = remote_id;
297 }
298
299 self.subscriptions.clear();
300 if let Some(remote_id) = remote_id {
301 let client = &self.client;
302 self.subscriptions.extend([
303 client.subscribe_to_entity(remote_id, cx, Self::handle_open_buffer),
304 client.subscribe_to_entity(remote_id, cx, Self::handle_close_buffer),
305 client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
306 client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
307 client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
308 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
309 client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
310 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
311 client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffer),
312 ]);
313 }
314 }
315
316 pub fn remote_id(&self) -> Option<u64> {
317 match &self.client_state {
318 ProjectClientState::Local { remote_id_rx, .. } => *remote_id_rx.borrow(),
319 ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
320 }
321 }
322
323 pub fn next_remote_id(&self) -> impl Future<Output = u64> {
324 let mut id = None;
325 let mut watch = None;
326 match &self.client_state {
327 ProjectClientState::Local { remote_id_rx, .. } => watch = Some(remote_id_rx.clone()),
328 ProjectClientState::Remote { remote_id, .. } => id = Some(*remote_id),
329 }
330
331 async move {
332 if let Some(id) = id {
333 return id;
334 }
335 let mut watch = watch.unwrap();
336 loop {
337 let id = *watch.borrow();
338 if let Some(id) = id {
339 return id;
340 }
341 watch.recv().await;
342 }
343 }
344 }
345
346 pub fn replica_id(&self) -> ReplicaId {
347 match &self.client_state {
348 ProjectClientState::Local { .. } => 0,
349 ProjectClientState::Remote { replica_id, .. } => *replica_id,
350 }
351 }
352
353 pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
354 &self.collaborators
355 }
356
357 pub fn worktrees(&self) -> &[ModelHandle<Worktree>] {
358 &self.worktrees
359 }
360
361 pub fn worktree_for_id(
362 &self,
363 id: WorktreeId,
364 cx: &AppContext,
365 ) -> Option<ModelHandle<Worktree>> {
366 self.worktrees
367 .iter()
368 .find(|worktree| worktree.read(cx).id() == id)
369 .cloned()
370 }
371
372 pub fn share(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
373 let rpc = self.client.clone();
374 cx.spawn(|this, mut cx| async move {
375 let project_id = this.update(&mut cx, |this, _| {
376 if let ProjectClientState::Local {
377 is_shared,
378 remote_id_rx,
379 ..
380 } = &mut this.client_state
381 {
382 *is_shared = true;
383 remote_id_rx
384 .borrow()
385 .ok_or_else(|| anyhow!("no project id"))
386 } else {
387 Err(anyhow!("can't share a remote project"))
388 }
389 })?;
390
391 rpc.request(proto::ShareProject { project_id }).await?;
392 let mut tasks = Vec::new();
393 this.update(&mut cx, |this, cx| {
394 for worktree in &this.worktrees {
395 worktree.update(cx, |worktree, cx| {
396 let worktree = worktree.as_local_mut().unwrap();
397 tasks.push(worktree.share(project_id, cx));
398 });
399 }
400 });
401 for task in tasks {
402 task.await?;
403 }
404 this.update(&mut cx, |_, cx| cx.notify());
405 Ok(())
406 })
407 }
408
409 pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
410 let rpc = self.client.clone();
411 cx.spawn(|this, mut cx| async move {
412 let project_id = this.update(&mut cx, |this, _| {
413 if let ProjectClientState::Local {
414 is_shared,
415 remote_id_rx,
416 ..
417 } = &mut this.client_state
418 {
419 *is_shared = false;
420 remote_id_rx
421 .borrow()
422 .ok_or_else(|| anyhow!("no project id"))
423 } else {
424 Err(anyhow!("can't share a remote project"))
425 }
426 })?;
427
428 rpc.send(proto::UnshareProject { project_id }).await?;
429 this.update(&mut cx, |this, cx| {
430 this.collaborators.clear();
431 for worktree in &this.worktrees {
432 worktree.update(cx, |worktree, _| {
433 worktree.as_local_mut().unwrap().unshare();
434 });
435 }
436 cx.notify()
437 });
438 Ok(())
439 })
440 }
441
442 pub fn is_read_only(&self) -> bool {
443 match &self.client_state {
444 ProjectClientState::Local { .. } => false,
445 ProjectClientState::Remote {
446 sharing_has_stopped,
447 ..
448 } => *sharing_has_stopped,
449 }
450 }
451
452 pub fn is_local(&self) -> bool {
453 match &self.client_state {
454 ProjectClientState::Local { .. } => true,
455 ProjectClientState::Remote { .. } => false,
456 }
457 }
458
459 pub fn open_buffer(
460 &self,
461 path: ProjectPath,
462 cx: &mut ModelContext<Self>,
463 ) -> Task<Result<ModelHandle<Buffer>>> {
464 if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
465 worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx))
466 } else {
467 cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) })
468 }
469 }
470
471 pub fn is_shared(&self) -> bool {
472 match &self.client_state {
473 ProjectClientState::Local { is_shared, .. } => *is_shared,
474 ProjectClientState::Remote { .. } => false,
475 }
476 }
477
478 pub fn add_local_worktree(
479 &mut self,
480 abs_path: impl AsRef<Path>,
481 cx: &mut ModelContext<Self>,
482 ) -> Task<Result<ModelHandle<Worktree>>> {
483 let fs = self.fs.clone();
484 let client = self.client.clone();
485 let user_store = self.user_store.clone();
486 let languages = self.languages.clone();
487 let path = Arc::from(abs_path.as_ref());
488 cx.spawn(|project, mut cx| async move {
489 let worktree =
490 Worktree::open_local(client.clone(), user_store, path, fs, languages, &mut cx)
491 .await?;
492
493 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
494 project.add_worktree(worktree.clone(), cx);
495 (project.remote_id(), project.is_shared())
496 });
497
498 if let Some(project_id) = remote_project_id {
499 let worktree_id = worktree.id() as u64;
500 let register_message = worktree.update(&mut cx, |worktree, _| {
501 let worktree = worktree.as_local_mut().unwrap();
502 proto::RegisterWorktree {
503 project_id,
504 worktree_id,
505 root_name: worktree.root_name().to_string(),
506 authorized_logins: worktree.authorized_logins(),
507 }
508 });
509 client.request(register_message).await?;
510 if is_shared {
511 worktree
512 .update(&mut cx, |worktree, cx| {
513 worktree.as_local_mut().unwrap().share(project_id, cx)
514 })
515 .await?;
516 }
517 }
518
519 Ok(worktree)
520 })
521 }
522
523 fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
524 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
525 cx.subscribe(&worktree, move |this, worktree, event, cx| match event {
526 worktree::Event::DiagnosticsUpdated(path) => {
527 cx.emit(Event::DiagnosticsUpdated(ProjectPath {
528 worktree_id: worktree.read(cx).id(),
529 path: path.clone(),
530 }));
531 }
532 worktree::Event::DiskBasedDiagnosticsUpdating => {
533 if this.pending_disk_based_diagnostics == 0 {
534 cx.emit(Event::DiskBasedDiagnosticsStarted);
535 }
536 this.pending_disk_based_diagnostics += 1;
537 }
538 worktree::Event::DiskBasedDiagnosticsUpdated => {
539 this.pending_disk_based_diagnostics -= 1;
540 cx.emit(Event::DiskBasedDiagnosticsUpdated {
541 worktree_id: worktree.read(cx).id(),
542 });
543 if this.pending_disk_based_diagnostics == 0 {
544 if this.pending_disk_based_diagnostics == 0 {
545 cx.emit(Event::DiskBasedDiagnosticsFinished);
546 }
547 }
548 }
549 })
550 .detach();
551 self.worktrees.push(worktree);
552 cx.notify();
553 }
554
555 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
556 let new_active_entry = entry.and_then(|project_path| {
557 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
558 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
559 Some(ProjectEntry {
560 worktree_id: project_path.worktree_id,
561 entry_id: entry.id,
562 })
563 });
564 if new_active_entry != self.active_entry {
565 self.active_entry = new_active_entry;
566 cx.emit(Event::ActiveEntryChanged(new_active_entry));
567 }
568 }
569
570 pub fn path_for_entry(&self, entry: ProjectEntry, cx: &AppContext) -> Option<ProjectPath> {
571 let worktree = self.worktree_for_id(entry.worktree_id, cx)?.read(cx);
572 Some(ProjectPath {
573 worktree_id: entry.worktree_id,
574 path: worktree.entry_for_id(entry.entry_id)?.path.clone(),
575 })
576 }
577
578 pub fn is_running_disk_based_diagnostics(&self) -> bool {
579 self.pending_disk_based_diagnostics > 0
580 }
581
582 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
583 let mut summary = DiagnosticSummary::default();
584 for (_, path_summary) in self.diagnostic_summaries(cx) {
585 summary.error_count += path_summary.error_count;
586 summary.warning_count += path_summary.warning_count;
587 summary.info_count += path_summary.info_count;
588 summary.hint_count += path_summary.hint_count;
589 }
590 summary
591 }
592
593 pub fn diagnostic_summaries<'a>(
594 &'a self,
595 cx: &'a AppContext,
596 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
597 self.worktrees.iter().flat_map(move |worktree| {
598 let worktree = worktree.read(cx);
599 let worktree_id = worktree.id();
600 worktree
601 .diagnostic_summaries()
602 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
603 })
604 }
605
606 pub fn active_entry(&self) -> Option<ProjectEntry> {
607 self.active_entry
608 }
609
610 // RPC message handlers
611
612 fn handle_unshare_project(
613 &mut self,
614 _: TypedEnvelope<proto::UnshareProject>,
615 _: Arc<Client>,
616 cx: &mut ModelContext<Self>,
617 ) -> Result<()> {
618 if let ProjectClientState::Remote {
619 sharing_has_stopped,
620 ..
621 } = &mut self.client_state
622 {
623 *sharing_has_stopped = true;
624 self.collaborators.clear();
625 cx.notify();
626 Ok(())
627 } else {
628 unreachable!()
629 }
630 }
631
632 fn handle_add_collaborator(
633 &mut self,
634 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
635 _: Arc<Client>,
636 cx: &mut ModelContext<Self>,
637 ) -> Result<()> {
638 let user_store = self.user_store.clone();
639 let collaborator = envelope
640 .payload
641 .collaborator
642 .take()
643 .ok_or_else(|| anyhow!("empty collaborator"))?;
644
645 cx.spawn(|this, mut cx| {
646 async move {
647 let collaborator =
648 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
649 this.update(&mut cx, |this, cx| {
650 this.collaborators
651 .insert(collaborator.peer_id, collaborator);
652 cx.notify();
653 });
654 Ok(())
655 }
656 .log_err()
657 })
658 .detach();
659
660 Ok(())
661 }
662
663 fn handle_remove_collaborator(
664 &mut self,
665 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
666 _: Arc<Client>,
667 cx: &mut ModelContext<Self>,
668 ) -> Result<()> {
669 let peer_id = PeerId(envelope.payload.peer_id);
670 let replica_id = self
671 .collaborators
672 .remove(&peer_id)
673 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
674 .replica_id;
675 for worktree in &self.worktrees {
676 worktree.update(cx, |worktree, cx| {
677 worktree.remove_collaborator(peer_id, replica_id, cx);
678 })
679 }
680 Ok(())
681 }
682
683 fn handle_share_worktree(
684 &mut self,
685 envelope: TypedEnvelope<proto::ShareWorktree>,
686 client: Arc<Client>,
687 cx: &mut ModelContext<Self>,
688 ) -> Result<()> {
689 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
690 let replica_id = self.replica_id();
691 let worktree = envelope
692 .payload
693 .worktree
694 .ok_or_else(|| anyhow!("invalid worktree"))?;
695 let user_store = self.user_store.clone();
696 let languages = self.languages.clone();
697 cx.spawn(|this, mut cx| {
698 async move {
699 let worktree = Worktree::remote(
700 remote_id, replica_id, worktree, client, user_store, languages, &mut cx,
701 )
702 .await?;
703 this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx));
704 Ok(())
705 }
706 .log_err()
707 })
708 .detach();
709 Ok(())
710 }
711
712 fn handle_unregister_worktree(
713 &mut self,
714 envelope: TypedEnvelope<proto::UnregisterWorktree>,
715 _: Arc<Client>,
716 cx: &mut ModelContext<Self>,
717 ) -> Result<()> {
718 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
719 self.worktrees
720 .retain(|worktree| worktree.read(cx).as_remote().unwrap().id() != worktree_id);
721 cx.notify();
722 Ok(())
723 }
724
725 fn handle_update_worktree(
726 &mut self,
727 envelope: TypedEnvelope<proto::UpdateWorktree>,
728 _: Arc<Client>,
729 cx: &mut ModelContext<Self>,
730 ) -> Result<()> {
731 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
732 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
733 worktree.update(cx, |worktree, cx| {
734 let worktree = worktree.as_remote_mut().unwrap();
735 worktree.update_from_remote(envelope, cx)
736 })?;
737 }
738 Ok(())
739 }
740
741 fn handle_update_diagnostic_summary(
742 &mut self,
743 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
744 _: Arc<Client>,
745 cx: &mut ModelContext<Self>,
746 ) -> Result<()> {
747 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
748 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
749 worktree.update(cx, |worktree, cx| {
750 worktree
751 .as_remote_mut()
752 .unwrap()
753 .update_diagnostic_summary(envelope, cx);
754 });
755 }
756 Ok(())
757 }
758
759 fn handle_disk_based_diagnostics_updating(
760 &mut self,
761 envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
762 _: Arc<Client>,
763 cx: &mut ModelContext<Self>,
764 ) -> Result<()> {
765 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
766 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
767 worktree.update(cx, |worktree, cx| {
768 worktree
769 .as_remote()
770 .unwrap()
771 .disk_based_diagnostics_updating(cx);
772 });
773 }
774 Ok(())
775 }
776
777 fn handle_disk_based_diagnostics_updated(
778 &mut self,
779 envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
780 _: Arc<Client>,
781 cx: &mut ModelContext<Self>,
782 ) -> Result<()> {
783 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
784 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
785 worktree.update(cx, |worktree, cx| {
786 worktree
787 .as_remote()
788 .unwrap()
789 .disk_based_diagnostics_updated(cx);
790 });
791 }
792 Ok(())
793 }
794
795 pub fn handle_update_buffer(
796 &mut self,
797 envelope: TypedEnvelope<proto::UpdateBuffer>,
798 _: Arc<Client>,
799 cx: &mut ModelContext<Self>,
800 ) -> Result<()> {
801 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
802 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
803 worktree.update(cx, |worktree, cx| {
804 worktree.handle_update_buffer(envelope, cx)
805 })?;
806 }
807 Ok(())
808 }
809
810 pub fn handle_save_buffer(
811 &mut self,
812 envelope: TypedEnvelope<proto::SaveBuffer>,
813 rpc: Arc<Client>,
814 cx: &mut ModelContext<Self>,
815 ) -> Result<()> {
816 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
817 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
818 worktree.update(cx, |worktree, cx| {
819 worktree.handle_save_buffer(envelope, rpc, cx)
820 })?;
821 }
822 Ok(())
823 }
824
825 pub fn handle_format_buffer(
826 &mut self,
827 envelope: TypedEnvelope<proto::FormatBuffer>,
828 rpc: Arc<Client>,
829 cx: &mut ModelContext<Self>,
830 ) -> Result<()> {
831 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
832 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
833 worktree.update(cx, |worktree, cx| {
834 worktree.handle_format_buffer(envelope, rpc, cx)
835 })?;
836 }
837 Ok(())
838 }
839
840 pub fn handle_open_buffer(
841 &mut self,
842 envelope: TypedEnvelope<proto::OpenBuffer>,
843 rpc: Arc<Client>,
844 cx: &mut ModelContext<Self>,
845 ) -> anyhow::Result<()> {
846 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
847 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
848 return worktree.update(cx, |worktree, cx| {
849 worktree.handle_open_buffer(envelope, rpc, cx)
850 });
851 } else {
852 Err(anyhow!("no such worktree"))
853 }
854 }
855
856 pub fn handle_close_buffer(
857 &mut self,
858 envelope: TypedEnvelope<proto::CloseBuffer>,
859 rpc: Arc<Client>,
860 cx: &mut ModelContext<Self>,
861 ) -> anyhow::Result<()> {
862 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
863 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
864 worktree.update(cx, |worktree, cx| {
865 worktree.handle_close_buffer(envelope, rpc, cx)
866 })?;
867 }
868 Ok(())
869 }
870
871 pub fn handle_buffer_saved(
872 &mut self,
873 envelope: TypedEnvelope<proto::BufferSaved>,
874 _: Arc<Client>,
875 cx: &mut ModelContext<Self>,
876 ) -> Result<()> {
877 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
878 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
879 worktree.update(cx, |worktree, cx| {
880 worktree.handle_buffer_saved(envelope, cx)
881 })?;
882 }
883 Ok(())
884 }
885
886 pub fn match_paths<'a>(
887 &self,
888 query: &'a str,
889 include_ignored: bool,
890 smart_case: bool,
891 max_results: usize,
892 cancel_flag: &'a AtomicBool,
893 cx: &AppContext,
894 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
895 let include_root_name = self.worktrees.len() > 1;
896 let candidate_sets = self
897 .worktrees
898 .iter()
899 .map(|worktree| CandidateSet {
900 snapshot: worktree.read(cx).snapshot(),
901 include_ignored,
902 include_root_name,
903 })
904 .collect::<Vec<_>>();
905
906 let background = cx.background().clone();
907 async move {
908 fuzzy::match_paths(
909 candidate_sets.as_slice(),
910 query,
911 smart_case,
912 max_results,
913 cancel_flag,
914 background,
915 )
916 .await
917 }
918 }
919}
920
921struct CandidateSet {
922 snapshot: Snapshot,
923 include_ignored: bool,
924 include_root_name: bool,
925}
926
927impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
928 type Candidates = CandidateSetIter<'a>;
929
930 fn id(&self) -> usize {
931 self.snapshot.id().to_usize()
932 }
933
934 fn len(&self) -> usize {
935 if self.include_ignored {
936 self.snapshot.file_count()
937 } else {
938 self.snapshot.visible_file_count()
939 }
940 }
941
942 fn prefix(&self) -> Arc<str> {
943 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
944 self.snapshot.root_name().into()
945 } else if self.include_root_name {
946 format!("{}/", self.snapshot.root_name()).into()
947 } else {
948 "".into()
949 }
950 }
951
952 fn candidates(&'a self, start: usize) -> Self::Candidates {
953 CandidateSetIter {
954 traversal: self.snapshot.files(self.include_ignored, start),
955 }
956 }
957}
958
959struct CandidateSetIter<'a> {
960 traversal: Traversal<'a>,
961}
962
963impl<'a> Iterator for CandidateSetIter<'a> {
964 type Item = PathMatchCandidate<'a>;
965
966 fn next(&mut self) -> Option<Self::Item> {
967 self.traversal.next().map(|entry| {
968 if let EntryKind::File(char_bag) = entry.kind {
969 PathMatchCandidate {
970 path: &entry.path,
971 char_bag,
972 }
973 } else {
974 unreachable!()
975 }
976 })
977 }
978}
979
980impl Entity for Project {
981 type Event = Event;
982
983 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
984 match &self.client_state {
985 ProjectClientState::Local { remote_id_rx, .. } => {
986 if let Some(project_id) = *remote_id_rx.borrow() {
987 let rpc = self.client.clone();
988 cx.spawn(|_| async move {
989 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
990 log::error!("error unregistering project: {}", err);
991 }
992 })
993 .detach();
994 }
995 }
996 ProjectClientState::Remote { remote_id, .. } => {
997 let rpc = self.client.clone();
998 let project_id = *remote_id;
999 cx.spawn(|_| async move {
1000 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
1001 log::error!("error leaving project: {}", err);
1002 }
1003 })
1004 .detach();
1005 }
1006 }
1007 }
1008}
1009
1010impl Collaborator {
1011 fn from_proto(
1012 message: proto::Collaborator,
1013 user_store: &ModelHandle<UserStore>,
1014 cx: &mut AsyncAppContext,
1015 ) -> impl Future<Output = Result<Self>> {
1016 let user = user_store.update(cx, |user_store, cx| {
1017 user_store.fetch_user(message.user_id, cx)
1018 });
1019
1020 async move {
1021 Ok(Self {
1022 peer_id: PeerId(message.peer_id),
1023 user: user.await?,
1024 replica_id: message.replica_id as ReplicaId,
1025 })
1026 }
1027 }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032 use super::*;
1033 use client::test::FakeHttpClient;
1034 use fs::RealFs;
1035 use gpui::TestAppContext;
1036 use language::LanguageRegistry;
1037 use serde_json::json;
1038 use std::{os::unix, path::PathBuf};
1039 use util::test::temp_tree;
1040
1041 #[gpui::test]
1042 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1043 let dir = temp_tree(json!({
1044 "root": {
1045 "apple": "",
1046 "banana": {
1047 "carrot": {
1048 "date": "",
1049 "endive": "",
1050 }
1051 },
1052 "fennel": {
1053 "grape": "",
1054 }
1055 }
1056 }));
1057
1058 let root_link_path = dir.path().join("root_link");
1059 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1060 unix::fs::symlink(
1061 &dir.path().join("root/fennel"),
1062 &dir.path().join("root/finnochio"),
1063 )
1064 .unwrap();
1065
1066 let project = build_project(&mut cx);
1067
1068 let tree = project
1069 .update(&mut cx, |project, cx| {
1070 project.add_local_worktree(&root_link_path, cx)
1071 })
1072 .await
1073 .unwrap();
1074
1075 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1076 .await;
1077 cx.read(|cx| {
1078 let tree = tree.read(cx);
1079 assert_eq!(tree.file_count(), 5);
1080 assert_eq!(
1081 tree.inode_for_path("fennel/grape"),
1082 tree.inode_for_path("finnochio/grape")
1083 );
1084 });
1085
1086 let cancel_flag = Default::default();
1087 let results = project
1088 .read_with(&cx, |project, cx| {
1089 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1090 })
1091 .await;
1092 assert_eq!(
1093 results
1094 .into_iter()
1095 .map(|result| result.path)
1096 .collect::<Vec<Arc<Path>>>(),
1097 vec![
1098 PathBuf::from("banana/carrot/date").into(),
1099 PathBuf::from("banana/carrot/endive").into(),
1100 ]
1101 );
1102 }
1103
1104 #[gpui::test]
1105 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1106 let dir = temp_tree(json!({
1107 "root": {
1108 "dir1": {},
1109 "dir2": {
1110 "dir3": {}
1111 }
1112 }
1113 }));
1114
1115 let project = build_project(&mut cx);
1116 let tree = project
1117 .update(&mut cx, |project, cx| {
1118 project.add_local_worktree(&dir.path(), cx)
1119 })
1120 .await
1121 .unwrap();
1122
1123 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1124 .await;
1125
1126 let cancel_flag = Default::default();
1127 let results = project
1128 .read_with(&cx, |project, cx| {
1129 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1130 })
1131 .await;
1132
1133 assert!(results.is_empty());
1134 }
1135
1136 fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1137 let languages = Arc::new(LanguageRegistry::new());
1138 let fs = Arc::new(RealFs);
1139 let http_client = FakeHttpClient::with_404_response();
1140 let client = client::Client::new(http_client.clone());
1141 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1142 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1143 }
1144}