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 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 cx.notify()
432 });
433 Ok(())
434 })
435 }
436
437 pub fn is_read_only(&self) -> bool {
438 match &self.client_state {
439 ProjectClientState::Local { .. } => false,
440 ProjectClientState::Remote {
441 sharing_has_stopped,
442 ..
443 } => *sharing_has_stopped,
444 }
445 }
446
447 pub fn is_local(&self) -> bool {
448 match &self.client_state {
449 ProjectClientState::Local { .. } => true,
450 ProjectClientState::Remote { .. } => false,
451 }
452 }
453
454 pub fn open_buffer(
455 &self,
456 path: ProjectPath,
457 cx: &mut ModelContext<Self>,
458 ) -> Task<Result<ModelHandle<Buffer>>> {
459 if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
460 worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx))
461 } else {
462 cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) })
463 }
464 }
465
466 pub fn is_shared(&self) -> bool {
467 match &self.client_state {
468 ProjectClientState::Local { is_shared, .. } => *is_shared,
469 ProjectClientState::Remote { .. } => false,
470 }
471 }
472
473 pub fn add_local_worktree(
474 &mut self,
475 abs_path: impl AsRef<Path>,
476 cx: &mut ModelContext<Self>,
477 ) -> Task<Result<ModelHandle<Worktree>>> {
478 let fs = self.fs.clone();
479 let client = self.client.clone();
480 let user_store = self.user_store.clone();
481 let languages = self.languages.clone();
482 let path = Arc::from(abs_path.as_ref());
483 cx.spawn(|project, mut cx| async move {
484 let worktree =
485 Worktree::open_local(client.clone(), user_store, path, fs, languages, &mut cx)
486 .await?;
487
488 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
489 project.add_worktree(worktree.clone(), cx);
490 (project.remote_id(), project.is_shared())
491 });
492
493 if let Some(project_id) = remote_project_id {
494 let worktree_id = worktree.id() as u64;
495 let register_message = worktree.update(&mut cx, |worktree, _| {
496 let worktree = worktree.as_local_mut().unwrap();
497 proto::RegisterWorktree {
498 project_id,
499 worktree_id,
500 root_name: worktree.root_name().to_string(),
501 authorized_logins: worktree.authorized_logins(),
502 }
503 });
504 client.request(register_message).await?;
505 if is_shared {
506 worktree
507 .update(&mut cx, |worktree, cx| {
508 worktree.as_local_mut().unwrap().share(project_id, cx)
509 })
510 .await?;
511 }
512 }
513
514 Ok(worktree)
515 })
516 }
517
518 fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
519 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
520 cx.subscribe(&worktree, move |this, worktree, event, cx| match event {
521 worktree::Event::DiagnosticsUpdated(path) => {
522 cx.emit(Event::DiagnosticsUpdated(ProjectPath {
523 worktree_id: worktree.read(cx).id(),
524 path: path.clone(),
525 }));
526 }
527 worktree::Event::DiskBasedDiagnosticsUpdating => {
528 if this.pending_disk_based_diagnostics == 0 {
529 cx.emit(Event::DiskBasedDiagnosticsStarted);
530 }
531 this.pending_disk_based_diagnostics += 1;
532 }
533 worktree::Event::DiskBasedDiagnosticsUpdated => {
534 this.pending_disk_based_diagnostics -= 1;
535 cx.emit(Event::DiskBasedDiagnosticsUpdated {
536 worktree_id: worktree.read(cx).id(),
537 });
538 if this.pending_disk_based_diagnostics == 0 {
539 if this.pending_disk_based_diagnostics == 0 {
540 cx.emit(Event::DiskBasedDiagnosticsFinished);
541 }
542 }
543 }
544 })
545 .detach();
546 self.worktrees.push(worktree);
547 cx.notify();
548 }
549
550 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
551 let new_active_entry = entry.and_then(|project_path| {
552 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
553 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
554 Some(ProjectEntry {
555 worktree_id: project_path.worktree_id,
556 entry_id: entry.id,
557 })
558 });
559 if new_active_entry != self.active_entry {
560 self.active_entry = new_active_entry;
561 cx.emit(Event::ActiveEntryChanged(new_active_entry));
562 }
563 }
564
565 pub fn is_running_disk_based_diagnostics(&self) -> bool {
566 self.pending_disk_based_diagnostics > 0
567 }
568
569 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
570 let mut summary = DiagnosticSummary::default();
571 for (_, path_summary) in self.diagnostic_summaries(cx) {
572 summary.error_count += path_summary.error_count;
573 summary.warning_count += path_summary.warning_count;
574 summary.info_count += path_summary.info_count;
575 summary.hint_count += path_summary.hint_count;
576 }
577 summary
578 }
579
580 pub fn diagnostic_summaries<'a>(
581 &'a self,
582 cx: &'a AppContext,
583 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
584 self.worktrees.iter().flat_map(move |worktree| {
585 let worktree = worktree.read(cx);
586 let worktree_id = worktree.id();
587 worktree
588 .diagnostic_summaries()
589 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
590 })
591 }
592
593 pub fn active_entry(&self) -> Option<ProjectEntry> {
594 self.active_entry
595 }
596
597 // RPC message handlers
598
599 fn handle_unshare_project(
600 &mut self,
601 _: TypedEnvelope<proto::UnshareProject>,
602 _: Arc<Client>,
603 cx: &mut ModelContext<Self>,
604 ) -> Result<()> {
605 if let ProjectClientState::Remote {
606 sharing_has_stopped,
607 ..
608 } = &mut self.client_state
609 {
610 *sharing_has_stopped = true;
611 self.collaborators.clear();
612 cx.notify();
613 Ok(())
614 } else {
615 unreachable!()
616 }
617 }
618
619 fn handle_add_collaborator(
620 &mut self,
621 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
622 _: Arc<Client>,
623 cx: &mut ModelContext<Self>,
624 ) -> Result<()> {
625 let user_store = self.user_store.clone();
626 let collaborator = envelope
627 .payload
628 .collaborator
629 .take()
630 .ok_or_else(|| anyhow!("empty collaborator"))?;
631
632 cx.spawn(|this, mut cx| {
633 async move {
634 let collaborator =
635 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
636 this.update(&mut cx, |this, cx| {
637 this.collaborators
638 .insert(collaborator.peer_id, collaborator);
639 cx.notify();
640 });
641 Ok(())
642 }
643 .log_err()
644 })
645 .detach();
646
647 Ok(())
648 }
649
650 fn handle_remove_collaborator(
651 &mut self,
652 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
653 _: Arc<Client>,
654 cx: &mut ModelContext<Self>,
655 ) -> Result<()> {
656 let peer_id = PeerId(envelope.payload.peer_id);
657 let replica_id = self
658 .collaborators
659 .remove(&peer_id)
660 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
661 .replica_id;
662 for worktree in &self.worktrees {
663 worktree.update(cx, |worktree, cx| {
664 worktree.remove_collaborator(peer_id, replica_id, cx);
665 })
666 }
667 Ok(())
668 }
669
670 fn handle_share_worktree(
671 &mut self,
672 envelope: TypedEnvelope<proto::ShareWorktree>,
673 client: Arc<Client>,
674 cx: &mut ModelContext<Self>,
675 ) -> Result<()> {
676 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
677 let replica_id = self.replica_id();
678 let worktree = envelope
679 .payload
680 .worktree
681 .ok_or_else(|| anyhow!("invalid worktree"))?;
682 let user_store = self.user_store.clone();
683 let languages = self.languages.clone();
684 cx.spawn(|this, mut cx| {
685 async move {
686 let worktree = Worktree::remote(
687 remote_id, replica_id, worktree, client, user_store, languages, &mut cx,
688 )
689 .await?;
690 this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx));
691 Ok(())
692 }
693 .log_err()
694 })
695 .detach();
696 Ok(())
697 }
698
699 fn handle_unregister_worktree(
700 &mut self,
701 envelope: TypedEnvelope<proto::UnregisterWorktree>,
702 _: Arc<Client>,
703 cx: &mut ModelContext<Self>,
704 ) -> Result<()> {
705 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
706 self.worktrees
707 .retain(|worktree| worktree.read(cx).as_remote().unwrap().id() != worktree_id);
708 cx.notify();
709 Ok(())
710 }
711
712 fn handle_update_worktree(
713 &mut self,
714 envelope: TypedEnvelope<proto::UpdateWorktree>,
715 _: Arc<Client>,
716 cx: &mut ModelContext<Self>,
717 ) -> Result<()> {
718 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
719 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
720 worktree.update(cx, |worktree, cx| {
721 let worktree = worktree.as_remote_mut().unwrap();
722 worktree.update_from_remote(envelope, cx)
723 })?;
724 }
725 Ok(())
726 }
727
728 fn handle_update_diagnostic_summary(
729 &mut self,
730 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
731 _: Arc<Client>,
732 cx: &mut ModelContext<Self>,
733 ) -> Result<()> {
734 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
735 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
736 worktree.update(cx, |worktree, cx| {
737 worktree
738 .as_remote_mut()
739 .unwrap()
740 .update_diagnostic_summary(envelope, cx);
741 });
742 }
743 Ok(())
744 }
745
746 fn handle_disk_based_diagnostics_updating(
747 &mut self,
748 envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
749 _: Arc<Client>,
750 cx: &mut ModelContext<Self>,
751 ) -> 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 worktree.update(cx, |worktree, cx| {
755 worktree
756 .as_remote()
757 .unwrap()
758 .disk_based_diagnostics_updating(cx);
759 });
760 }
761 Ok(())
762 }
763
764 fn handle_disk_based_diagnostics_updated(
765 &mut self,
766 envelope: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
767 _: Arc<Client>,
768 cx: &mut ModelContext<Self>,
769 ) -> Result<()> {
770 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
771 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
772 worktree.update(cx, |worktree, cx| {
773 worktree
774 .as_remote()
775 .unwrap()
776 .disk_based_diagnostics_updated(cx);
777 });
778 }
779 Ok(())
780 }
781
782 pub fn handle_update_buffer(
783 &mut self,
784 envelope: TypedEnvelope<proto::UpdateBuffer>,
785 _: Arc<Client>,
786 cx: &mut ModelContext<Self>,
787 ) -> Result<()> {
788 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
789 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
790 worktree.update(cx, |worktree, cx| {
791 worktree.handle_update_buffer(envelope, cx)
792 })?;
793 }
794 Ok(())
795 }
796
797 pub fn handle_save_buffer(
798 &mut self,
799 envelope: TypedEnvelope<proto::SaveBuffer>,
800 rpc: Arc<Client>,
801 cx: &mut ModelContext<Self>,
802 ) -> Result<()> {
803 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
804 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
805 worktree.update(cx, |worktree, cx| {
806 worktree.handle_save_buffer(envelope, rpc, cx)
807 })?;
808 }
809 Ok(())
810 }
811
812 pub fn handle_format_buffer(
813 &mut self,
814 envelope: TypedEnvelope<proto::FormatBuffer>,
815 rpc: Arc<Client>,
816 cx: &mut ModelContext<Self>,
817 ) -> Result<()> {
818 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
819 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
820 worktree.update(cx, |worktree, cx| {
821 worktree.handle_format_buffer(envelope, rpc, cx)
822 })?;
823 }
824 Ok(())
825 }
826
827 pub fn handle_open_buffer(
828 &mut self,
829 envelope: TypedEnvelope<proto::OpenBuffer>,
830 rpc: Arc<Client>,
831 cx: &mut ModelContext<Self>,
832 ) -> anyhow::Result<()> {
833 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
834 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
835 return worktree.update(cx, |worktree, cx| {
836 worktree.handle_open_buffer(envelope, rpc, cx)
837 });
838 } else {
839 Err(anyhow!("no such worktree"))
840 }
841 }
842
843 pub fn handle_close_buffer(
844 &mut self,
845 envelope: TypedEnvelope<proto::CloseBuffer>,
846 rpc: Arc<Client>,
847 cx: &mut ModelContext<Self>,
848 ) -> anyhow::Result<()> {
849 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
850 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
851 worktree.update(cx, |worktree, cx| {
852 worktree.handle_close_buffer(envelope, rpc, cx)
853 })?;
854 }
855 Ok(())
856 }
857
858 pub fn handle_buffer_saved(
859 &mut self,
860 envelope: TypedEnvelope<proto::BufferSaved>,
861 _: Arc<Client>,
862 cx: &mut ModelContext<Self>,
863 ) -> Result<()> {
864 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
865 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
866 worktree.update(cx, |worktree, cx| {
867 worktree.handle_buffer_saved(envelope, cx)
868 })?;
869 }
870 Ok(())
871 }
872
873 pub fn match_paths<'a>(
874 &self,
875 query: &'a str,
876 include_ignored: bool,
877 smart_case: bool,
878 max_results: usize,
879 cancel_flag: &'a AtomicBool,
880 cx: &AppContext,
881 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
882 let include_root_name = self.worktrees.len() > 1;
883 let candidate_sets = self
884 .worktrees
885 .iter()
886 .map(|worktree| CandidateSet {
887 snapshot: worktree.read(cx).snapshot(),
888 include_ignored,
889 include_root_name,
890 })
891 .collect::<Vec<_>>();
892
893 let background = cx.background().clone();
894 async move {
895 fuzzy::match_paths(
896 candidate_sets.as_slice(),
897 query,
898 smart_case,
899 max_results,
900 cancel_flag,
901 background,
902 )
903 .await
904 }
905 }
906}
907
908struct CandidateSet {
909 snapshot: Snapshot,
910 include_ignored: bool,
911 include_root_name: bool,
912}
913
914impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
915 type Candidates = CandidateSetIter<'a>;
916
917 fn id(&self) -> usize {
918 self.snapshot.id().to_usize()
919 }
920
921 fn len(&self) -> usize {
922 if self.include_ignored {
923 self.snapshot.file_count()
924 } else {
925 self.snapshot.visible_file_count()
926 }
927 }
928
929 fn prefix(&self) -> Arc<str> {
930 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
931 self.snapshot.root_name().into()
932 } else if self.include_root_name {
933 format!("{}/", self.snapshot.root_name()).into()
934 } else {
935 "".into()
936 }
937 }
938
939 fn candidates(&'a self, start: usize) -> Self::Candidates {
940 CandidateSetIter {
941 traversal: self.snapshot.files(self.include_ignored, start),
942 }
943 }
944}
945
946struct CandidateSetIter<'a> {
947 traversal: Traversal<'a>,
948}
949
950impl<'a> Iterator for CandidateSetIter<'a> {
951 type Item = PathMatchCandidate<'a>;
952
953 fn next(&mut self) -> Option<Self::Item> {
954 self.traversal.next().map(|entry| {
955 if let EntryKind::File(char_bag) = entry.kind {
956 PathMatchCandidate {
957 path: &entry.path,
958 char_bag,
959 }
960 } else {
961 unreachable!()
962 }
963 })
964 }
965}
966
967impl Entity for Project {
968 type Event = Event;
969
970 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
971 match &self.client_state {
972 ProjectClientState::Local { remote_id_rx, .. } => {
973 if let Some(project_id) = *remote_id_rx.borrow() {
974 let rpc = self.client.clone();
975 cx.spawn(|_| async move {
976 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
977 log::error!("error unregistering project: {}", err);
978 }
979 })
980 .detach();
981 }
982 }
983 ProjectClientState::Remote { remote_id, .. } => {
984 let rpc = self.client.clone();
985 let project_id = *remote_id;
986 cx.spawn(|_| async move {
987 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
988 log::error!("error leaving project: {}", err);
989 }
990 })
991 .detach();
992 }
993 }
994 }
995}
996
997impl Collaborator {
998 fn from_proto(
999 message: proto::Collaborator,
1000 user_store: &ModelHandle<UserStore>,
1001 cx: &mut AsyncAppContext,
1002 ) -> impl Future<Output = Result<Self>> {
1003 let user = user_store.update(cx, |user_store, cx| {
1004 user_store.fetch_user(message.user_id, cx)
1005 });
1006
1007 async move {
1008 Ok(Self {
1009 peer_id: PeerId(message.peer_id),
1010 user: user.await?,
1011 replica_id: message.replica_id as ReplicaId,
1012 })
1013 }
1014 }
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019 use super::*;
1020 use client::test::FakeHttpClient;
1021 use fs::RealFs;
1022 use gpui::TestAppContext;
1023 use language::LanguageRegistry;
1024 use serde_json::json;
1025 use std::{os::unix, path::PathBuf};
1026 use util::test::temp_tree;
1027
1028 #[gpui::test]
1029 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1030 let dir = temp_tree(json!({
1031 "root": {
1032 "apple": "",
1033 "banana": {
1034 "carrot": {
1035 "date": "",
1036 "endive": "",
1037 }
1038 },
1039 "fennel": {
1040 "grape": "",
1041 }
1042 }
1043 }));
1044
1045 let root_link_path = dir.path().join("root_link");
1046 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1047 unix::fs::symlink(
1048 &dir.path().join("root/fennel"),
1049 &dir.path().join("root/finnochio"),
1050 )
1051 .unwrap();
1052
1053 let project = build_project(&mut cx);
1054
1055 let tree = project
1056 .update(&mut cx, |project, cx| {
1057 project.add_local_worktree(&root_link_path, cx)
1058 })
1059 .await
1060 .unwrap();
1061
1062 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1063 .await;
1064 cx.read(|cx| {
1065 let tree = tree.read(cx);
1066 assert_eq!(tree.file_count(), 5);
1067 assert_eq!(
1068 tree.inode_for_path("fennel/grape"),
1069 tree.inode_for_path("finnochio/grape")
1070 );
1071 });
1072
1073 let cancel_flag = Default::default();
1074 let results = project
1075 .read_with(&cx, |project, cx| {
1076 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1077 })
1078 .await;
1079 assert_eq!(
1080 results
1081 .into_iter()
1082 .map(|result| result.path)
1083 .collect::<Vec<Arc<Path>>>(),
1084 vec![
1085 PathBuf::from("banana/carrot/date").into(),
1086 PathBuf::from("banana/carrot/endive").into(),
1087 ]
1088 );
1089 }
1090
1091 #[gpui::test]
1092 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1093 let dir = temp_tree(json!({
1094 "root": {
1095 "dir1": {},
1096 "dir2": {
1097 "dir3": {}
1098 }
1099 }
1100 }));
1101
1102 let project = build_project(&mut cx);
1103 let tree = project
1104 .update(&mut cx, |project, cx| {
1105 project.add_local_worktree(&dir.path(), cx)
1106 })
1107 .await
1108 .unwrap();
1109
1110 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1111 .await;
1112
1113 let cancel_flag = Default::default();
1114 let results = project
1115 .read_with(&cx, |project, cx| {
1116 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1117 })
1118 .await;
1119
1120 assert!(results.is_empty());
1121 }
1122
1123 fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1124 let languages = Arc::new(LanguageRegistry::new());
1125 let fs = Arc::new(RealFs);
1126 let http_client = FakeHttpClient::with_404_response();
1127 let client = client::Client::new(http_client.clone());
1128 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1129 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1130 }
1131}