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::{hash_map, HashMap, HashSet};
9use futures::Future;
10use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
11use gpui::{
12 AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
13 WeakModelHandle,
14};
15use language::{
16 Bias, Buffer, DiagnosticEntry, File as _, Language, LanguageRegistry, ToOffset, ToPointUtf16,
17};
18use lsp::{DiagnosticSeverity, LanguageServer};
19use postage::{prelude::Stream, watch};
20use smol::block_on;
21use std::{
22 ops::Range,
23 path::{Path, PathBuf},
24 sync::{atomic::AtomicBool, Arc},
25};
26use util::{ResultExt, TryFutureExt as _};
27
28pub use fs::*;
29pub use worktree::*;
30
31pub struct Project {
32 worktrees: Vec<WorktreeHandle>,
33 active_entry: Option<ProjectEntry>,
34 languages: Arc<LanguageRegistry>,
35 language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
36 client: Arc<client::Client>,
37 user_store: ModelHandle<UserStore>,
38 fs: Arc<dyn Fs>,
39 client_state: ProjectClientState,
40 collaborators: HashMap<PeerId, Collaborator>,
41 subscriptions: Vec<client::Subscription>,
42 language_servers_with_diagnostics_running: isize,
43}
44
45enum WorktreeHandle {
46 Strong(ModelHandle<Worktree>),
47 Weak(WeakModelHandle<Worktree>),
48}
49
50enum ProjectClientState {
51 Local {
52 is_shared: bool,
53 remote_id_tx: watch::Sender<Option<u64>>,
54 remote_id_rx: watch::Receiver<Option<u64>>,
55 _maintain_remote_id_task: Task<Option<()>>,
56 },
57 Remote {
58 sharing_has_stopped: bool,
59 remote_id: u64,
60 replica_id: ReplicaId,
61 },
62}
63
64#[derive(Clone, Debug)]
65pub struct Collaborator {
66 pub user: Arc<User>,
67 pub peer_id: PeerId,
68 pub replica_id: ReplicaId,
69}
70
71#[derive(Clone, Debug, PartialEq)]
72pub enum Event {
73 ActiveEntryChanged(Option<ProjectEntry>),
74 WorktreeRemoved(WorktreeId),
75 DiskBasedDiagnosticsStarted,
76 DiskBasedDiagnosticsUpdated,
77 DiskBasedDiagnosticsFinished,
78 DiagnosticsUpdated(ProjectPath),
79}
80
81#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
82pub struct ProjectPath {
83 pub worktree_id: WorktreeId,
84 pub path: Arc<Path>,
85}
86
87#[derive(Clone, Debug, Default, PartialEq)]
88pub struct DiagnosticSummary {
89 pub error_count: usize,
90 pub warning_count: usize,
91 pub info_count: usize,
92 pub hint_count: usize,
93}
94
95#[derive(Debug)]
96pub struct Definition {
97 pub source_range: Option<Range<language::Anchor>>,
98 pub target_buffer: ModelHandle<Buffer>,
99 pub target_range: Range<language::Anchor>,
100}
101
102impl DiagnosticSummary {
103 fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
104 let mut this = Self {
105 error_count: 0,
106 warning_count: 0,
107 info_count: 0,
108 hint_count: 0,
109 };
110
111 for entry in diagnostics {
112 if entry.diagnostic.is_primary {
113 match entry.diagnostic.severity {
114 DiagnosticSeverity::ERROR => this.error_count += 1,
115 DiagnosticSeverity::WARNING => this.warning_count += 1,
116 DiagnosticSeverity::INFORMATION => this.info_count += 1,
117 DiagnosticSeverity::HINT => this.hint_count += 1,
118 _ => {}
119 }
120 }
121 }
122
123 this
124 }
125
126 pub fn to_proto(&self, path: Arc<Path>) -> proto::DiagnosticSummary {
127 proto::DiagnosticSummary {
128 path: path.to_string_lossy().to_string(),
129 error_count: self.error_count as u32,
130 warning_count: self.warning_count as u32,
131 info_count: self.info_count as u32,
132 hint_count: self.hint_count as u32,
133 }
134 }
135}
136
137#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
138pub struct ProjectEntry {
139 pub worktree_id: WorktreeId,
140 pub entry_id: usize,
141}
142
143impl Project {
144 pub fn local(
145 client: Arc<Client>,
146 user_store: ModelHandle<UserStore>,
147 languages: Arc<LanguageRegistry>,
148 fs: Arc<dyn Fs>,
149 cx: &mut MutableAppContext,
150 ) -> ModelHandle<Self> {
151 cx.add_model(|cx: &mut ModelContext<Self>| {
152 let (remote_id_tx, remote_id_rx) = watch::channel();
153 let _maintain_remote_id_task = cx.spawn_weak({
154 let rpc = client.clone();
155 move |this, mut cx| {
156 async move {
157 let mut status = rpc.status();
158 while let Some(status) = status.recv().await {
159 if let Some(this) = this.upgrade(&cx) {
160 let remote_id = if let client::Status::Connected { .. } = status {
161 let response = rpc.request(proto::RegisterProject {}).await?;
162 Some(response.project_id)
163 } else {
164 None
165 };
166
167 if let Some(project_id) = remote_id {
168 let mut registrations = Vec::new();
169 this.update(&mut cx, |this, cx| {
170 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
171 registrations.push(worktree.update(
172 cx,
173 |worktree, cx| {
174 let worktree = worktree.as_local_mut().unwrap();
175 worktree.register(project_id, cx)
176 },
177 ));
178 }
179 });
180 for registration in registrations {
181 registration.await?;
182 }
183 }
184 this.update(&mut cx, |this, cx| this.set_remote_id(remote_id, cx));
185 }
186 }
187 Ok(())
188 }
189 .log_err()
190 }
191 });
192
193 Self {
194 worktrees: Default::default(),
195 collaborators: Default::default(),
196 client_state: ProjectClientState::Local {
197 is_shared: false,
198 remote_id_tx,
199 remote_id_rx,
200 _maintain_remote_id_task,
201 },
202 subscriptions: Vec::new(),
203 active_entry: None,
204 languages,
205 client,
206 user_store,
207 fs,
208 language_servers_with_diagnostics_running: 0,
209 language_servers: Default::default(),
210 }
211 })
212 }
213
214 pub async fn remote(
215 remote_id: u64,
216 client: Arc<Client>,
217 user_store: ModelHandle<UserStore>,
218 languages: Arc<LanguageRegistry>,
219 fs: Arc<dyn Fs>,
220 cx: &mut AsyncAppContext,
221 ) -> Result<ModelHandle<Self>> {
222 client.authenticate_and_connect(&cx).await?;
223
224 let response = client
225 .request(proto::JoinProject {
226 project_id: remote_id,
227 })
228 .await?;
229
230 let replica_id = response.replica_id as ReplicaId;
231
232 let mut worktrees = Vec::new();
233 for worktree in response.worktrees {
234 worktrees.push(
235 Worktree::remote(
236 remote_id,
237 replica_id,
238 worktree,
239 client.clone(),
240 user_store.clone(),
241 cx,
242 )
243 .await?,
244 );
245 }
246
247 let user_ids = response
248 .collaborators
249 .iter()
250 .map(|peer| peer.user_id)
251 .collect();
252 user_store
253 .update(cx, |user_store, cx| user_store.load_users(user_ids, cx))
254 .await?;
255 let mut collaborators = HashMap::default();
256 for message in response.collaborators {
257 let collaborator = Collaborator::from_proto(message, &user_store, cx).await?;
258 collaborators.insert(collaborator.peer_id, collaborator);
259 }
260
261 Ok(cx.add_model(|cx| {
262 let mut this = Self {
263 worktrees: Vec::new(),
264 active_entry: None,
265 collaborators,
266 languages,
267 user_store,
268 fs,
269 subscriptions: vec![
270 client.subscribe_to_entity(remote_id, cx, Self::handle_unshare_project),
271 client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
272 client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
273 client.subscribe_to_entity(remote_id, cx, Self::handle_share_worktree),
274 client.subscribe_to_entity(remote_id, cx, Self::handle_unregister_worktree),
275 client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
276 client.subscribe_to_entity(
277 remote_id,
278 cx,
279 Self::handle_update_diagnostic_summary,
280 ),
281 client.subscribe_to_entity(
282 remote_id,
283 cx,
284 Self::handle_disk_based_diagnostics_updating,
285 ),
286 client.subscribe_to_entity(
287 remote_id,
288 cx,
289 Self::handle_disk_based_diagnostics_updated,
290 ),
291 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
292 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
293 ],
294 client,
295 client_state: ProjectClientState::Remote {
296 sharing_has_stopped: false,
297 remote_id,
298 replica_id,
299 },
300 language_servers_with_diagnostics_running: 0,
301 language_servers: Default::default(),
302 };
303 for worktree in worktrees {
304 this.add_worktree(&worktree, cx);
305 }
306 this
307 }))
308 }
309
310 fn set_remote_id(&mut self, remote_id: Option<u64>, cx: &mut ModelContext<Self>) {
311 if let ProjectClientState::Local { remote_id_tx, .. } = &mut self.client_state {
312 *remote_id_tx.borrow_mut() = remote_id;
313 }
314
315 self.subscriptions.clear();
316 if let Some(remote_id) = remote_id {
317 let client = &self.client;
318 self.subscriptions.extend([
319 client.subscribe_to_entity(remote_id, cx, Self::handle_open_buffer),
320 client.subscribe_to_entity(remote_id, cx, Self::handle_close_buffer),
321 client.subscribe_to_entity(remote_id, cx, Self::handle_add_collaborator),
322 client.subscribe_to_entity(remote_id, cx, Self::handle_remove_collaborator),
323 client.subscribe_to_entity(remote_id, cx, Self::handle_update_worktree),
324 client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
325 client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
326 client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
327 client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffer),
328 ]);
329 }
330 }
331
332 pub fn remote_id(&self) -> Option<u64> {
333 match &self.client_state {
334 ProjectClientState::Local { remote_id_rx, .. } => *remote_id_rx.borrow(),
335 ProjectClientState::Remote { remote_id, .. } => Some(*remote_id),
336 }
337 }
338
339 pub fn next_remote_id(&self) -> impl Future<Output = u64> {
340 let mut id = None;
341 let mut watch = None;
342 match &self.client_state {
343 ProjectClientState::Local { remote_id_rx, .. } => watch = Some(remote_id_rx.clone()),
344 ProjectClientState::Remote { remote_id, .. } => id = Some(*remote_id),
345 }
346
347 async move {
348 if let Some(id) = id {
349 return id;
350 }
351 let mut watch = watch.unwrap();
352 loop {
353 let id = *watch.borrow();
354 if let Some(id) = id {
355 return id;
356 }
357 watch.recv().await;
358 }
359 }
360 }
361
362 pub fn replica_id(&self) -> ReplicaId {
363 match &self.client_state {
364 ProjectClientState::Local { .. } => 0,
365 ProjectClientState::Remote { replica_id, .. } => *replica_id,
366 }
367 }
368
369 pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
370 &self.collaborators
371 }
372
373 pub fn worktrees<'a>(
374 &'a self,
375 cx: &'a AppContext,
376 ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
377 self.worktrees
378 .iter()
379 .filter_map(move |worktree| worktree.upgrade(cx))
380 }
381
382 pub fn worktree_for_id(
383 &self,
384 id: WorktreeId,
385 cx: &AppContext,
386 ) -> Option<ModelHandle<Worktree>> {
387 self.worktrees(cx)
388 .find(|worktree| worktree.read(cx).id() == id)
389 }
390
391 pub fn share(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
392 let rpc = self.client.clone();
393 cx.spawn(|this, mut cx| async move {
394 let project_id = this.update(&mut cx, |this, _| {
395 if let ProjectClientState::Local {
396 is_shared,
397 remote_id_rx,
398 ..
399 } = &mut this.client_state
400 {
401 *is_shared = true;
402 remote_id_rx
403 .borrow()
404 .ok_or_else(|| anyhow!("no project id"))
405 } else {
406 Err(anyhow!("can't share a remote project"))
407 }
408 })?;
409
410 rpc.request(proto::ShareProject { project_id }).await?;
411 let mut tasks = Vec::new();
412 this.update(&mut cx, |this, cx| {
413 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
414 worktree.update(cx, |worktree, cx| {
415 let worktree = worktree.as_local_mut().unwrap();
416 tasks.push(worktree.share(cx));
417 });
418 }
419 });
420 for task in tasks {
421 task.await?;
422 }
423 this.update(&mut cx, |_, cx| cx.notify());
424 Ok(())
425 })
426 }
427
428 pub fn unshare(&self, cx: &mut ModelContext<Self>) -> Task<anyhow::Result<()>> {
429 let rpc = self.client.clone();
430 cx.spawn(|this, mut cx| async move {
431 let project_id = this.update(&mut cx, |this, _| {
432 if let ProjectClientState::Local {
433 is_shared,
434 remote_id_rx,
435 ..
436 } = &mut this.client_state
437 {
438 *is_shared = false;
439 remote_id_rx
440 .borrow()
441 .ok_or_else(|| anyhow!("no project id"))
442 } else {
443 Err(anyhow!("can't share a remote project"))
444 }
445 })?;
446
447 rpc.send(proto::UnshareProject { project_id }).await?;
448 this.update(&mut cx, |this, cx| {
449 this.collaborators.clear();
450 for worktree in this.worktrees(cx).collect::<Vec<_>>() {
451 worktree.update(cx, |worktree, _| {
452 worktree.as_local_mut().unwrap().unshare();
453 });
454 }
455 cx.notify()
456 });
457 Ok(())
458 })
459 }
460
461 pub fn is_read_only(&self) -> bool {
462 match &self.client_state {
463 ProjectClientState::Local { .. } => false,
464 ProjectClientState::Remote {
465 sharing_has_stopped,
466 ..
467 } => *sharing_has_stopped,
468 }
469 }
470
471 pub fn is_local(&self) -> bool {
472 match &self.client_state {
473 ProjectClientState::Local { .. } => true,
474 ProjectClientState::Remote { .. } => false,
475 }
476 }
477
478 pub fn open_buffer(
479 &mut self,
480 path: ProjectPath,
481 cx: &mut ModelContext<Self>,
482 ) -> Task<Result<ModelHandle<Buffer>>> {
483 let worktree = if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
484 worktree
485 } else {
486 return cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) });
487 };
488 let buffer_task = worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx));
489 cx.spawn(|this, mut cx| async move {
490 let (buffer, buffer_is_new) = buffer_task.await?;
491 if buffer_is_new {
492 this.update(&mut cx, |this, cx| {
493 this.assign_language_to_buffer(worktree, buffer.clone(), cx)
494 });
495 }
496 Ok(buffer)
497 })
498 }
499
500 pub fn save_buffer_as(
501 &self,
502 buffer: ModelHandle<Buffer>,
503 abs_path: PathBuf,
504 cx: &mut ModelContext<Project>,
505 ) -> Task<Result<()>> {
506 let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, false, cx);
507 cx.spawn(|this, mut cx| async move {
508 let (worktree, path) = worktree_task.await?;
509 worktree
510 .update(&mut cx, |worktree, cx| {
511 worktree
512 .as_local_mut()
513 .unwrap()
514 .save_buffer_as(buffer.clone(), path, cx)
515 })
516 .await?;
517 this.update(&mut cx, |this, cx| {
518 this.assign_language_to_buffer(worktree, buffer, cx)
519 });
520 Ok(())
521 })
522 }
523
524 fn assign_language_to_buffer(
525 &mut self,
526 worktree: ModelHandle<Worktree>,
527 buffer: ModelHandle<Buffer>,
528 cx: &mut ModelContext<Self>,
529 ) -> Option<()> {
530 // Set the buffer's language
531 let full_path = buffer.read(cx).file()?.full_path();
532 let language = self.languages.select_language(&full_path)?.clone();
533 buffer.update(cx, |buffer, cx| {
534 buffer.set_language(Some(language.clone()), cx);
535 });
536
537 // For local worktrees, start a language server if needed.
538 let worktree = worktree.read(cx);
539 let worktree_id = worktree.id();
540 let worktree_abs_path = worktree.as_local()?.abs_path().clone();
541 let language_server = match self
542 .language_servers
543 .entry((worktree_id, language.name().to_string()))
544 {
545 hash_map::Entry::Occupied(e) => Some(e.get().clone()),
546 hash_map::Entry::Vacant(e) => {
547 Self::start_language_server(self.client.clone(), language, &worktree_abs_path, cx)
548 .map(|server| e.insert(server).clone())
549 }
550 };
551
552 buffer.update(cx, |buffer, cx| {
553 buffer.set_language_server(language_server, cx)
554 });
555
556 None
557 }
558
559 fn start_language_server(
560 rpc: Arc<Client>,
561 language: Arc<Language>,
562 worktree_path: &Path,
563 cx: &mut ModelContext<Self>,
564 ) -> Option<Arc<LanguageServer>> {
565 enum LspEvent {
566 DiagnosticsStart,
567 DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
568 DiagnosticsFinish,
569 }
570
571 let language_server = language
572 .start_server(worktree_path, cx)
573 .log_err()
574 .flatten()?;
575 let disk_based_sources = language
576 .disk_based_diagnostic_sources()
577 .cloned()
578 .unwrap_or_default();
579 let disk_based_diagnostics_progress_token =
580 language.disk_based_diagnostics_progress_token().cloned();
581 let has_disk_based_diagnostic_progress_token =
582 disk_based_diagnostics_progress_token.is_some();
583 let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
584
585 // Listen for `PublishDiagnostics` notifications.
586 language_server
587 .on_notification::<lsp::notification::PublishDiagnostics, _>({
588 let diagnostics_tx = diagnostics_tx.clone();
589 move |params| {
590 if !has_disk_based_diagnostic_progress_token {
591 block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
592 }
593 block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))).ok();
594 if !has_disk_based_diagnostic_progress_token {
595 block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
596 }
597 }
598 })
599 .detach();
600
601 // Listen for `Progress` notifications. Send an event when the language server
602 // transitions between running jobs and not running any jobs.
603 let mut running_jobs_for_this_server: i32 = 0;
604 language_server
605 .on_notification::<lsp::notification::Progress, _>(move |params| {
606 let token = match params.token {
607 lsp::NumberOrString::Number(_) => None,
608 lsp::NumberOrString::String(token) => Some(token),
609 };
610
611 if token == disk_based_diagnostics_progress_token {
612 match params.value {
613 lsp::ProgressParamsValue::WorkDone(progress) => match progress {
614 lsp::WorkDoneProgress::Begin(_) => {
615 running_jobs_for_this_server += 1;
616 if running_jobs_for_this_server == 1 {
617 block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
618 }
619 }
620 lsp::WorkDoneProgress::End(_) => {
621 running_jobs_for_this_server -= 1;
622 if running_jobs_for_this_server == 0 {
623 block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
624 }
625 }
626 _ => {}
627 },
628 }
629 }
630 })
631 .detach();
632
633 // Process all the LSP events.
634 cx.spawn_weak(|this, mut cx| async move {
635 while let Ok(message) = diagnostics_rx.recv().await {
636 let this = cx.read(|cx| this.upgrade(cx))?;
637 match message {
638 LspEvent::DiagnosticsStart => {
639 let send = this.update(&mut cx, |this, cx| {
640 this.disk_based_diagnostics_started(cx);
641 this.remote_id().map(|project_id| {
642 rpc.send(proto::DiskBasedDiagnosticsUpdating { project_id })
643 })
644 });
645 if let Some(send) = send {
646 send.await.log_err();
647 }
648 }
649 LspEvent::DiagnosticsUpdate(params) => {
650 this.update(&mut cx, |this, cx| {
651 this.update_diagnostics(params, &disk_based_sources, cx)
652 .log_err();
653 });
654 }
655 LspEvent::DiagnosticsFinish => {
656 let send = this.update(&mut cx, |this, cx| {
657 this.disk_based_diagnostics_finished(cx);
658 this.remote_id().map(|project_id| {
659 rpc.send(proto::DiskBasedDiagnosticsUpdated { project_id })
660 })
661 });
662 if let Some(send) = send {
663 send.await.log_err();
664 }
665 }
666 }
667 }
668 Some(())
669 })
670 .detach();
671
672 Some(language_server)
673 }
674
675 fn update_diagnostics(
676 &mut self,
677 diagnostics: lsp::PublishDiagnosticsParams,
678 disk_based_sources: &HashSet<String>,
679 cx: &mut ModelContext<Self>,
680 ) -> Result<()> {
681 let path = diagnostics
682 .uri
683 .to_file_path()
684 .map_err(|_| anyhow!("URI is not a file"))?;
685 let (worktree, relative_path) = self
686 .find_worktree_for_abs_path(&path, cx)
687 .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?;
688 let project_path = ProjectPath {
689 worktree_id: worktree.read(cx).id(),
690 path: relative_path.into(),
691 };
692 worktree.update(cx, |worktree, cx| {
693 worktree.as_local_mut().unwrap().update_diagnostics(
694 project_path.path.clone(),
695 diagnostics,
696 disk_based_sources,
697 cx,
698 )
699 })?;
700 cx.emit(Event::DiagnosticsUpdated(project_path));
701 Ok(())
702 }
703
704 pub fn definition<T: ToOffset>(
705 &self,
706 source_buffer_handle: &ModelHandle<Buffer>,
707 position: T,
708 cx: &mut ModelContext<Self>,
709 ) -> Task<Result<Vec<Definition>>> {
710 let source_buffer_handle = source_buffer_handle.clone();
711 let buffer = source_buffer_handle.read(cx);
712 let worktree;
713 let buffer_abs_path;
714 if let Some(file) = File::from_dyn(buffer.file()) {
715 worktree = file.worktree.clone();
716 buffer_abs_path = file.abs_path();
717 } else {
718 return Task::ready(Err(anyhow!("buffer does not belong to any worktree")));
719 };
720
721 if worktree.read(cx).as_local().is_some() {
722 let point = buffer.offset_to_point_utf16(position.to_offset(buffer));
723 let buffer_abs_path = buffer_abs_path.unwrap();
724 let lang_name;
725 let lang_server;
726 if let Some(lang) = buffer.language() {
727 lang_name = lang.name().to_string();
728 if let Some(server) = self
729 .language_servers
730 .get(&(worktree.read(cx).id(), lang_name.clone()))
731 {
732 lang_server = server.clone();
733 } else {
734 return Task::ready(Err(anyhow!("buffer does not have a language server")));
735 };
736 } else {
737 return Task::ready(Err(anyhow!("buffer does not have a language")));
738 }
739
740 cx.spawn(|this, mut cx| async move {
741 let response = lang_server
742 .request::<lsp::request::GotoDefinition>(lsp::GotoDefinitionParams {
743 text_document_position_params: lsp::TextDocumentPositionParams {
744 text_document: lsp::TextDocumentIdentifier::new(
745 lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
746 ),
747 position: lsp::Position::new(point.row, point.column),
748 },
749 work_done_progress_params: Default::default(),
750 partial_result_params: Default::default(),
751 })
752 .await?;
753
754 let mut definitions = Vec::new();
755 if let Some(response) = response {
756 let mut unresolved_locations = Vec::new();
757 match response {
758 lsp::GotoDefinitionResponse::Scalar(loc) => {
759 unresolved_locations.push((None, loc.uri, loc.range));
760 }
761 lsp::GotoDefinitionResponse::Array(locs) => {
762 unresolved_locations
763 .extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
764 }
765 lsp::GotoDefinitionResponse::Link(links) => {
766 unresolved_locations.extend(links.into_iter().map(|l| {
767 (
768 l.origin_selection_range,
769 l.target_uri,
770 l.target_selection_range,
771 )
772 }));
773 }
774 }
775
776 for (source_range, target_uri, target_range) in unresolved_locations {
777 let abs_path = target_uri
778 .to_file_path()
779 .map_err(|_| anyhow!("invalid target path"))?;
780
781 let (worktree, relative_path) = if let Some(result) = this
782 .read_with(&cx, |this, cx| {
783 this.find_worktree_for_abs_path(&abs_path, cx)
784 }) {
785 result
786 } else {
787 let (worktree, relative_path) = this
788 .update(&mut cx, |this, cx| {
789 this.create_worktree_for_abs_path(&abs_path, true, cx)
790 })
791 .await?;
792 this.update(&mut cx, |this, cx| {
793 this.language_servers.insert(
794 (worktree.read(cx).id(), lang_name.clone()),
795 lang_server.clone(),
796 );
797 });
798 (worktree, relative_path)
799 };
800
801 let project_path = ProjectPath {
802 worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
803 path: relative_path.into(),
804 };
805 let target_buffer_handle = this
806 .update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
807 .await?;
808 cx.read(|cx| {
809 let source_buffer = source_buffer_handle.read(cx);
810 let target_buffer = target_buffer_handle.read(cx);
811 let source_range = source_range.map(|range| {
812 let start = source_buffer
813 .clip_point_utf16(range.start.to_point_utf16(), Bias::Left);
814 let end = source_buffer
815 .clip_point_utf16(range.end.to_point_utf16(), Bias::Left);
816 source_buffer.anchor_after(start)..source_buffer.anchor_before(end)
817 });
818 let target_start = target_buffer
819 .clip_point_utf16(target_range.start.to_point_utf16(), Bias::Left);
820 let target_end = target_buffer
821 .clip_point_utf16(target_range.end.to_point_utf16(), Bias::Left);
822 definitions.push(Definition {
823 source_range,
824 target_buffer: target_buffer_handle,
825 target_range: target_buffer.anchor_after(target_start)
826 ..target_buffer.anchor_before(target_end),
827 });
828 });
829 }
830 }
831
832 Ok(definitions)
833 })
834 } else {
835 todo!()
836 }
837 }
838
839 pub fn find_or_create_worktree_for_abs_path(
840 &self,
841 abs_path: impl AsRef<Path>,
842 weak: bool,
843 cx: &mut ModelContext<Self>,
844 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
845 let abs_path = abs_path.as_ref();
846 if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) {
847 Task::ready(Ok((tree.clone(), relative_path.into())))
848 } else {
849 self.create_worktree_for_abs_path(abs_path, weak, cx)
850 }
851 }
852
853 fn create_worktree_for_abs_path(
854 &self,
855 abs_path: &Path,
856 weak: bool,
857 cx: &mut ModelContext<Self>,
858 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
859 let worktree = self.add_local_worktree(abs_path, weak, cx);
860 cx.background().spawn(async move {
861 let worktree = worktree.await?;
862 Ok((worktree, PathBuf::new()))
863 })
864 }
865
866 fn find_worktree_for_abs_path(
867 &self,
868 abs_path: &Path,
869 cx: &AppContext,
870 ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
871 for tree in self.worktrees(cx) {
872 if let Some(relative_path) = tree
873 .read(cx)
874 .as_local()
875 .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
876 {
877 return Some((tree.clone(), relative_path.into()));
878 }
879 }
880 None
881 }
882
883 pub fn is_shared(&self) -> bool {
884 match &self.client_state {
885 ProjectClientState::Local { is_shared, .. } => *is_shared,
886 ProjectClientState::Remote { .. } => false,
887 }
888 }
889
890 fn add_local_worktree(
891 &self,
892 abs_path: impl AsRef<Path>,
893 weak: bool,
894 cx: &mut ModelContext<Self>,
895 ) -> Task<Result<ModelHandle<Worktree>>> {
896 let fs = self.fs.clone();
897 let client = self.client.clone();
898 let user_store = self.user_store.clone();
899 let path = Arc::from(abs_path.as_ref());
900 cx.spawn(|project, mut cx| async move {
901 let worktree =
902 Worktree::open_local(client.clone(), user_store, path, weak, fs, &mut cx).await?;
903
904 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
905 project.add_worktree(&worktree, cx);
906 (project.remote_id(), project.is_shared())
907 });
908
909 if let Some(project_id) = remote_project_id {
910 worktree
911 .update(&mut cx, |worktree, cx| {
912 worktree.as_local_mut().unwrap().register(project_id, cx)
913 })
914 .await?;
915 if is_shared {
916 worktree
917 .update(&mut cx, |worktree, cx| {
918 worktree.as_local_mut().unwrap().share(cx)
919 })
920 .await?;
921 }
922 }
923
924 Ok(worktree)
925 })
926 }
927
928 pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext<Self>) {
929 self.worktrees.retain(|worktree| {
930 worktree
931 .upgrade(cx)
932 .map_or(false, |w| w.read(cx).id() != id)
933 });
934 cx.notify();
935 }
936
937 fn add_worktree(&mut self, worktree: &ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
938 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
939
940 let push_weak_handle = {
941 let worktree = worktree.read(cx);
942 worktree.is_local() && worktree.is_weak()
943 };
944 if push_weak_handle {
945 cx.observe_release(&worktree, |this, cx| {
946 this.worktrees
947 .retain(|worktree| worktree.upgrade(cx).is_some());
948 cx.notify();
949 })
950 .detach();
951 self.worktrees
952 .push(WorktreeHandle::Weak(worktree.downgrade()));
953 } else {
954 self.worktrees
955 .push(WorktreeHandle::Strong(worktree.clone()));
956 }
957 cx.notify();
958 }
959
960 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
961 let new_active_entry = entry.and_then(|project_path| {
962 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
963 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
964 Some(ProjectEntry {
965 worktree_id: project_path.worktree_id,
966 entry_id: entry.id,
967 })
968 });
969 if new_active_entry != self.active_entry {
970 self.active_entry = new_active_entry;
971 cx.emit(Event::ActiveEntryChanged(new_active_entry));
972 }
973 }
974
975 pub fn path_for_entry(&self, entry: ProjectEntry, cx: &AppContext) -> Option<ProjectPath> {
976 let worktree = self.worktree_for_id(entry.worktree_id, cx)?.read(cx);
977 Some(ProjectPath {
978 worktree_id: entry.worktree_id,
979 path: worktree.entry_for_id(entry.entry_id)?.path.clone(),
980 })
981 }
982
983 pub fn is_running_disk_based_diagnostics(&self) -> bool {
984 self.language_servers_with_diagnostics_running > 0
985 }
986
987 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
988 let mut summary = DiagnosticSummary::default();
989 for (_, path_summary) in self.diagnostic_summaries(cx) {
990 summary.error_count += path_summary.error_count;
991 summary.warning_count += path_summary.warning_count;
992 summary.info_count += path_summary.info_count;
993 summary.hint_count += path_summary.hint_count;
994 }
995 summary
996 }
997
998 pub fn diagnostic_summaries<'a>(
999 &'a self,
1000 cx: &'a AppContext,
1001 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
1002 self.worktrees(cx).flat_map(move |worktree| {
1003 let worktree = worktree.read(cx);
1004 let worktree_id = worktree.id();
1005 worktree
1006 .diagnostic_summaries()
1007 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
1008 })
1009 }
1010
1011 fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
1012 self.language_servers_with_diagnostics_running += 1;
1013 if self.language_servers_with_diagnostics_running == 1 {
1014 cx.emit(Event::DiskBasedDiagnosticsStarted);
1015 }
1016 }
1017
1018 fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
1019 cx.emit(Event::DiskBasedDiagnosticsUpdated);
1020 self.language_servers_with_diagnostics_running -= 1;
1021 if self.language_servers_with_diagnostics_running == 0 {
1022 cx.emit(Event::DiskBasedDiagnosticsFinished);
1023 }
1024 }
1025
1026 pub fn active_entry(&self) -> Option<ProjectEntry> {
1027 self.active_entry
1028 }
1029
1030 // RPC message handlers
1031
1032 fn handle_unshare_project(
1033 &mut self,
1034 _: TypedEnvelope<proto::UnshareProject>,
1035 _: Arc<Client>,
1036 cx: &mut ModelContext<Self>,
1037 ) -> Result<()> {
1038 if let ProjectClientState::Remote {
1039 sharing_has_stopped,
1040 ..
1041 } = &mut self.client_state
1042 {
1043 *sharing_has_stopped = true;
1044 self.collaborators.clear();
1045 cx.notify();
1046 Ok(())
1047 } else {
1048 unreachable!()
1049 }
1050 }
1051
1052 fn handle_add_collaborator(
1053 &mut self,
1054 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
1055 _: Arc<Client>,
1056 cx: &mut ModelContext<Self>,
1057 ) -> Result<()> {
1058 let user_store = self.user_store.clone();
1059 let collaborator = envelope
1060 .payload
1061 .collaborator
1062 .take()
1063 .ok_or_else(|| anyhow!("empty collaborator"))?;
1064
1065 cx.spawn(|this, mut cx| {
1066 async move {
1067 let collaborator =
1068 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
1069 this.update(&mut cx, |this, cx| {
1070 this.collaborators
1071 .insert(collaborator.peer_id, collaborator);
1072 cx.notify();
1073 });
1074 Ok(())
1075 }
1076 .log_err()
1077 })
1078 .detach();
1079
1080 Ok(())
1081 }
1082
1083 fn handle_remove_collaborator(
1084 &mut self,
1085 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
1086 _: Arc<Client>,
1087 cx: &mut ModelContext<Self>,
1088 ) -> Result<()> {
1089 let peer_id = PeerId(envelope.payload.peer_id);
1090 let replica_id = self
1091 .collaborators
1092 .remove(&peer_id)
1093 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
1094 .replica_id;
1095 for worktree in self.worktrees(cx).collect::<Vec<_>>() {
1096 worktree.update(cx, |worktree, cx| {
1097 worktree.remove_collaborator(peer_id, replica_id, cx);
1098 })
1099 }
1100 Ok(())
1101 }
1102
1103 fn handle_share_worktree(
1104 &mut self,
1105 envelope: TypedEnvelope<proto::ShareWorktree>,
1106 client: Arc<Client>,
1107 cx: &mut ModelContext<Self>,
1108 ) -> Result<()> {
1109 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
1110 let replica_id = self.replica_id();
1111 let worktree = envelope
1112 .payload
1113 .worktree
1114 .ok_or_else(|| anyhow!("invalid worktree"))?;
1115 let user_store = self.user_store.clone();
1116 cx.spawn(|this, mut cx| {
1117 async move {
1118 let worktree =
1119 Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx)
1120 .await?;
1121 this.update(&mut cx, |this, cx| this.add_worktree(&worktree, cx));
1122 Ok(())
1123 }
1124 .log_err()
1125 })
1126 .detach();
1127 Ok(())
1128 }
1129
1130 fn handle_unregister_worktree(
1131 &mut self,
1132 envelope: TypedEnvelope<proto::UnregisterWorktree>,
1133 _: Arc<Client>,
1134 cx: &mut ModelContext<Self>,
1135 ) -> Result<()> {
1136 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1137 self.remove_worktree(worktree_id, cx);
1138 Ok(())
1139 }
1140
1141 fn handle_update_worktree(
1142 &mut self,
1143 envelope: TypedEnvelope<proto::UpdateWorktree>,
1144 _: Arc<Client>,
1145 cx: &mut ModelContext<Self>,
1146 ) -> Result<()> {
1147 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1148 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1149 worktree.update(cx, |worktree, cx| {
1150 let worktree = worktree.as_remote_mut().unwrap();
1151 worktree.update_from_remote(envelope, cx)
1152 })?;
1153 }
1154 Ok(())
1155 }
1156
1157 fn handle_update_diagnostic_summary(
1158 &mut self,
1159 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
1160 _: Arc<Client>,
1161 cx: &mut ModelContext<Self>,
1162 ) -> Result<()> {
1163 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1164 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1165 if let Some(summary) = envelope.payload.summary {
1166 let project_path = ProjectPath {
1167 worktree_id,
1168 path: Path::new(&summary.path).into(),
1169 };
1170 worktree.update(cx, |worktree, _| {
1171 worktree
1172 .as_remote_mut()
1173 .unwrap()
1174 .update_diagnostic_summary(project_path.path.clone(), &summary);
1175 });
1176 cx.emit(Event::DiagnosticsUpdated(project_path));
1177 }
1178 }
1179 Ok(())
1180 }
1181
1182 fn handle_disk_based_diagnostics_updating(
1183 &mut self,
1184 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
1185 _: Arc<Client>,
1186 cx: &mut ModelContext<Self>,
1187 ) -> Result<()> {
1188 self.disk_based_diagnostics_started(cx);
1189 Ok(())
1190 }
1191
1192 fn handle_disk_based_diagnostics_updated(
1193 &mut self,
1194 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
1195 _: Arc<Client>,
1196 cx: &mut ModelContext<Self>,
1197 ) -> Result<()> {
1198 self.disk_based_diagnostics_finished(cx);
1199 Ok(())
1200 }
1201
1202 pub fn handle_update_buffer(
1203 &mut self,
1204 envelope: TypedEnvelope<proto::UpdateBuffer>,
1205 _: Arc<Client>,
1206 cx: &mut ModelContext<Self>,
1207 ) -> Result<()> {
1208 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1209 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1210 worktree.update(cx, |worktree, cx| {
1211 worktree.handle_update_buffer(envelope, cx)
1212 })?;
1213 }
1214 Ok(())
1215 }
1216
1217 pub fn handle_save_buffer(
1218 &mut self,
1219 envelope: TypedEnvelope<proto::SaveBuffer>,
1220 rpc: Arc<Client>,
1221 cx: &mut ModelContext<Self>,
1222 ) -> Result<()> {
1223 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1224 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1225 worktree.update(cx, |worktree, cx| {
1226 worktree.handle_save_buffer(envelope, rpc, cx)
1227 })?;
1228 }
1229 Ok(())
1230 }
1231
1232 pub fn handle_format_buffer(
1233 &mut self,
1234 envelope: TypedEnvelope<proto::FormatBuffer>,
1235 rpc: Arc<Client>,
1236 cx: &mut ModelContext<Self>,
1237 ) -> Result<()> {
1238 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1239 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1240 worktree.update(cx, |worktree, cx| {
1241 worktree.handle_format_buffer(envelope, rpc, cx)
1242 })?;
1243 }
1244 Ok(())
1245 }
1246
1247 pub fn handle_open_buffer(
1248 &mut self,
1249 envelope: TypedEnvelope<proto::OpenBuffer>,
1250 rpc: Arc<Client>,
1251 cx: &mut ModelContext<Self>,
1252 ) -> anyhow::Result<()> {
1253 let receipt = envelope.receipt();
1254 let peer_id = envelope.original_sender_id()?;
1255 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1256 let worktree = self
1257 .worktree_for_id(worktree_id, cx)
1258 .ok_or_else(|| anyhow!("no such worktree"))?;
1259
1260 let task = self.open_buffer(
1261 ProjectPath {
1262 worktree_id,
1263 path: PathBuf::from(envelope.payload.path).into(),
1264 },
1265 cx,
1266 );
1267 cx.spawn(|_, mut cx| {
1268 async move {
1269 let buffer = task.await?;
1270 let response = worktree.update(&mut cx, |worktree, cx| {
1271 worktree
1272 .as_local_mut()
1273 .unwrap()
1274 .open_remote_buffer(peer_id, buffer, cx)
1275 });
1276 rpc.respond(receipt, response).await?;
1277 Ok(())
1278 }
1279 .log_err()
1280 })
1281 .detach();
1282 Ok(())
1283 }
1284
1285 pub fn handle_close_buffer(
1286 &mut self,
1287 envelope: TypedEnvelope<proto::CloseBuffer>,
1288 _: Arc<Client>,
1289 cx: &mut ModelContext<Self>,
1290 ) -> anyhow::Result<()> {
1291 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1292 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1293 worktree.update(cx, |worktree, cx| {
1294 worktree
1295 .as_local_mut()
1296 .unwrap()
1297 .close_remote_buffer(envelope, cx)
1298 })?;
1299 }
1300 Ok(())
1301 }
1302
1303 pub fn handle_buffer_saved(
1304 &mut self,
1305 envelope: TypedEnvelope<proto::BufferSaved>,
1306 _: Arc<Client>,
1307 cx: &mut ModelContext<Self>,
1308 ) -> Result<()> {
1309 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1310 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1311 worktree.update(cx, |worktree, cx| {
1312 worktree.handle_buffer_saved(envelope, cx)
1313 })?;
1314 }
1315 Ok(())
1316 }
1317
1318 pub fn match_paths<'a>(
1319 &self,
1320 query: &'a str,
1321 include_ignored: bool,
1322 smart_case: bool,
1323 max_results: usize,
1324 cancel_flag: &'a AtomicBool,
1325 cx: &AppContext,
1326 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
1327 let include_root_name = self.worktrees.len() > 1;
1328 let candidate_sets = self
1329 .worktrees(cx)
1330 .map(|worktree| CandidateSet {
1331 snapshot: worktree.read(cx).snapshot(),
1332 include_ignored,
1333 include_root_name,
1334 })
1335 .collect::<Vec<_>>();
1336
1337 let background = cx.background().clone();
1338 async move {
1339 fuzzy::match_paths(
1340 candidate_sets.as_slice(),
1341 query,
1342 smart_case,
1343 max_results,
1344 cancel_flag,
1345 background,
1346 )
1347 .await
1348 }
1349 }
1350}
1351
1352impl WorktreeHandle {
1353 pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
1354 match self {
1355 WorktreeHandle::Strong(handle) => Some(handle.clone()),
1356 WorktreeHandle::Weak(handle) => handle.upgrade(cx),
1357 }
1358 }
1359}
1360
1361struct CandidateSet {
1362 snapshot: Snapshot,
1363 include_ignored: bool,
1364 include_root_name: bool,
1365}
1366
1367impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
1368 type Candidates = CandidateSetIter<'a>;
1369
1370 fn id(&self) -> usize {
1371 self.snapshot.id().to_usize()
1372 }
1373
1374 fn len(&self) -> usize {
1375 if self.include_ignored {
1376 self.snapshot.file_count()
1377 } else {
1378 self.snapshot.visible_file_count()
1379 }
1380 }
1381
1382 fn prefix(&self) -> Arc<str> {
1383 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
1384 self.snapshot.root_name().into()
1385 } else if self.include_root_name {
1386 format!("{}/", self.snapshot.root_name()).into()
1387 } else {
1388 "".into()
1389 }
1390 }
1391
1392 fn candidates(&'a self, start: usize) -> Self::Candidates {
1393 CandidateSetIter {
1394 traversal: self.snapshot.files(self.include_ignored, start),
1395 }
1396 }
1397}
1398
1399struct CandidateSetIter<'a> {
1400 traversal: Traversal<'a>,
1401}
1402
1403impl<'a> Iterator for CandidateSetIter<'a> {
1404 type Item = PathMatchCandidate<'a>;
1405
1406 fn next(&mut self) -> Option<Self::Item> {
1407 self.traversal.next().map(|entry| {
1408 if let EntryKind::File(char_bag) = entry.kind {
1409 PathMatchCandidate {
1410 path: &entry.path,
1411 char_bag,
1412 }
1413 } else {
1414 unreachable!()
1415 }
1416 })
1417 }
1418}
1419
1420impl Entity for Project {
1421 type Event = Event;
1422
1423 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
1424 match &self.client_state {
1425 ProjectClientState::Local { remote_id_rx, .. } => {
1426 if let Some(project_id) = *remote_id_rx.borrow() {
1427 let rpc = self.client.clone();
1428 cx.spawn(|_| async move {
1429 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
1430 log::error!("error unregistering project: {}", err);
1431 }
1432 })
1433 .detach();
1434 }
1435 }
1436 ProjectClientState::Remote { remote_id, .. } => {
1437 let rpc = self.client.clone();
1438 let project_id = *remote_id;
1439 cx.spawn(|_| async move {
1440 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
1441 log::error!("error leaving project: {}", err);
1442 }
1443 })
1444 .detach();
1445 }
1446 }
1447 }
1448
1449 fn app_will_quit(
1450 &mut self,
1451 _: &mut MutableAppContext,
1452 ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
1453 use futures::FutureExt;
1454
1455 let shutdown_futures = self
1456 .language_servers
1457 .drain()
1458 .filter_map(|(_, server)| server.shutdown())
1459 .collect::<Vec<_>>();
1460 Some(
1461 async move {
1462 futures::future::join_all(shutdown_futures).await;
1463 }
1464 .boxed(),
1465 )
1466 }
1467}
1468
1469impl Collaborator {
1470 fn from_proto(
1471 message: proto::Collaborator,
1472 user_store: &ModelHandle<UserStore>,
1473 cx: &mut AsyncAppContext,
1474 ) -> impl Future<Output = Result<Self>> {
1475 let user = user_store.update(cx, |user_store, cx| {
1476 user_store.fetch_user(message.user_id, cx)
1477 });
1478
1479 async move {
1480 Ok(Self {
1481 peer_id: PeerId(message.peer_id),
1482 user: user.await?,
1483 replica_id: message.replica_id as ReplicaId,
1484 })
1485 }
1486 }
1487}
1488
1489#[cfg(test)]
1490mod tests {
1491 use super::{Event, *};
1492 use client::test::FakeHttpClient;
1493 use fs::RealFs;
1494 use futures::StreamExt;
1495 use gpui::{test::subscribe, TestAppContext};
1496 use language::{
1497 tree_sitter_rust, Diagnostic, LanguageConfig, LanguageRegistry, LanguageServerConfig, Point,
1498 };
1499 use lsp::Url;
1500 use serde_json::json;
1501 use std::{os::unix, path::PathBuf};
1502 use util::test::temp_tree;
1503
1504 #[gpui::test]
1505 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1506 let dir = temp_tree(json!({
1507 "root": {
1508 "apple": "",
1509 "banana": {
1510 "carrot": {
1511 "date": "",
1512 "endive": "",
1513 }
1514 },
1515 "fennel": {
1516 "grape": "",
1517 }
1518 }
1519 }));
1520
1521 let root_link_path = dir.path().join("root_link");
1522 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1523 unix::fs::symlink(
1524 &dir.path().join("root/fennel"),
1525 &dir.path().join("root/finnochio"),
1526 )
1527 .unwrap();
1528
1529 let project = build_project(&mut cx);
1530
1531 let tree = project
1532 .update(&mut cx, |project, cx| {
1533 project.add_local_worktree(&root_link_path, false, cx)
1534 })
1535 .await
1536 .unwrap();
1537
1538 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1539 .await;
1540 cx.read(|cx| {
1541 let tree = tree.read(cx);
1542 assert_eq!(tree.file_count(), 5);
1543 assert_eq!(
1544 tree.inode_for_path("fennel/grape"),
1545 tree.inode_for_path("finnochio/grape")
1546 );
1547 });
1548
1549 let cancel_flag = Default::default();
1550 let results = project
1551 .read_with(&cx, |project, cx| {
1552 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1553 })
1554 .await;
1555 assert_eq!(
1556 results
1557 .into_iter()
1558 .map(|result| result.path)
1559 .collect::<Vec<Arc<Path>>>(),
1560 vec![
1561 PathBuf::from("banana/carrot/date").into(),
1562 PathBuf::from("banana/carrot/endive").into(),
1563 ]
1564 );
1565 }
1566
1567 #[gpui::test]
1568 async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
1569 let (language_server_config, mut fake_server) =
1570 LanguageServerConfig::fake(cx.background()).await;
1571 let progress_token = language_server_config
1572 .disk_based_diagnostics_progress_token
1573 .clone()
1574 .unwrap();
1575
1576 let mut languages = LanguageRegistry::new();
1577 languages.add(Arc::new(Language::new(
1578 LanguageConfig {
1579 name: "Rust".to_string(),
1580 path_suffixes: vec!["rs".to_string()],
1581 language_server: Some(language_server_config),
1582 ..Default::default()
1583 },
1584 Some(tree_sitter_rust::language()),
1585 )));
1586
1587 let dir = temp_tree(json!({
1588 "a.rs": "fn a() { A }",
1589 "b.rs": "const y: i32 = 1",
1590 }));
1591
1592 let http_client = FakeHttpClient::with_404_response();
1593 let client = Client::new(http_client.clone());
1594 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1595
1596 let project = cx.update(|cx| {
1597 Project::local(
1598 client,
1599 user_store,
1600 Arc::new(languages),
1601 Arc::new(RealFs),
1602 cx,
1603 )
1604 });
1605
1606 let tree = project
1607 .update(&mut cx, |project, cx| {
1608 project.add_local_worktree(dir.path(), false, cx)
1609 })
1610 .await
1611 .unwrap();
1612 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
1613
1614 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1615 .await;
1616
1617 // Cause worktree to start the fake language server
1618 let _buffer = project
1619 .update(&mut cx, |project, cx| {
1620 project.open_buffer(
1621 ProjectPath {
1622 worktree_id,
1623 path: Path::new("b.rs").into(),
1624 },
1625 cx,
1626 )
1627 })
1628 .await
1629 .unwrap();
1630
1631 let mut events = subscribe(&project, &mut cx);
1632
1633 fake_server.start_progress(&progress_token).await;
1634 assert_eq!(
1635 events.next().await.unwrap(),
1636 Event::DiskBasedDiagnosticsStarted
1637 );
1638
1639 fake_server.start_progress(&progress_token).await;
1640 fake_server.end_progress(&progress_token).await;
1641 fake_server.start_progress(&progress_token).await;
1642
1643 fake_server
1644 .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
1645 uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
1646 version: None,
1647 diagnostics: vec![lsp::Diagnostic {
1648 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1649 severity: Some(lsp::DiagnosticSeverity::ERROR),
1650 message: "undefined variable 'A'".to_string(),
1651 ..Default::default()
1652 }],
1653 })
1654 .await;
1655 assert_eq!(
1656 events.next().await.unwrap(),
1657 Event::DiagnosticsUpdated(ProjectPath {
1658 worktree_id,
1659 path: Arc::from(Path::new("a.rs"))
1660 })
1661 );
1662
1663 fake_server.end_progress(&progress_token).await;
1664 fake_server.end_progress(&progress_token).await;
1665 assert_eq!(
1666 events.next().await.unwrap(),
1667 Event::DiskBasedDiagnosticsUpdated
1668 );
1669 assert_eq!(
1670 events.next().await.unwrap(),
1671 Event::DiskBasedDiagnosticsFinished
1672 );
1673
1674 let (buffer, _) = tree
1675 .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx))
1676 .await
1677 .unwrap();
1678
1679 buffer.read_with(&cx, |buffer, _| {
1680 let snapshot = buffer.snapshot();
1681 let diagnostics = snapshot
1682 .diagnostics_in_range::<_, Point>(0..buffer.len())
1683 .collect::<Vec<_>>();
1684 assert_eq!(
1685 diagnostics,
1686 &[DiagnosticEntry {
1687 range: Point::new(0, 9)..Point::new(0, 10),
1688 diagnostic: Diagnostic {
1689 severity: lsp::DiagnosticSeverity::ERROR,
1690 message: "undefined variable 'A'".to_string(),
1691 group_id: 0,
1692 is_primary: true,
1693 ..Default::default()
1694 }
1695 }]
1696 )
1697 });
1698 }
1699
1700 #[gpui::test]
1701 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1702 let dir = temp_tree(json!({
1703 "root": {
1704 "dir1": {},
1705 "dir2": {
1706 "dir3": {}
1707 }
1708 }
1709 }));
1710
1711 let project = build_project(&mut cx);
1712 let tree = project
1713 .update(&mut cx, |project, cx| {
1714 project.add_local_worktree(&dir.path(), false, cx)
1715 })
1716 .await
1717 .unwrap();
1718
1719 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1720 .await;
1721
1722 let cancel_flag = Default::default();
1723 let results = project
1724 .read_with(&cx, |project, cx| {
1725 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1726 })
1727 .await;
1728
1729 assert!(results.is_empty());
1730 }
1731
1732 fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1733 let languages = Arc::new(LanguageRegistry::new());
1734 let fs = Arc::new(RealFs);
1735 let http_client = FakeHttpClient::with_404_response();
1736 let client = client::Client::new(http_client.clone());
1737 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1738 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1739 }
1740}