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