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