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