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