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