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