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