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: ProjectPath,
480 cx: &mut ModelContext<Self>,
481 ) -> Task<Result<ModelHandle<Buffer>>> {
482 let worktree = if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
483 worktree
484 } else {
485 return cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) });
486 };
487 let buffer_task = worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx));
488 cx.spawn(|this, mut cx| async move {
489 let (buffer, buffer_is_new) = buffer_task.await?;
490 if buffer_is_new {
491 this.update(&mut cx, |this, cx| {
492 this.assign_language_to_buffer(worktree, buffer.clone(), cx)
493 });
494 }
495 Ok(buffer)
496 })
497 }
498
499 pub fn save_buffer_as(
500 &self,
501 buffer: ModelHandle<Buffer>,
502 abs_path: PathBuf,
503 cx: &mut ModelContext<Project>,
504 ) -> Task<Result<()>> {
505 let worktree_task = self.find_or_create_worktree_for_abs_path(&abs_path, false, cx);
506 cx.spawn(|this, mut cx| async move {
507 let (worktree, path) = worktree_task.await?;
508 worktree
509 .update(&mut cx, |worktree, cx| {
510 worktree
511 .as_local_mut()
512 .unwrap()
513 .save_buffer_as(buffer.clone(), path, cx)
514 })
515 .await?;
516 this.update(&mut cx, |this, cx| {
517 this.assign_language_to_buffer(worktree, buffer, cx)
518 });
519 Ok(())
520 })
521 }
522
523 fn assign_language_to_buffer(
524 &mut self,
525 worktree: ModelHandle<Worktree>,
526 buffer: ModelHandle<Buffer>,
527 cx: &mut ModelContext<Self>,
528 ) -> Option<()> {
529 // Set the buffer's language
530 let full_path = buffer.read(cx).file()?.full_path();
531 let language = self.languages.select_language(&full_path)?.clone();
532 buffer.update(cx, |buffer, cx| {
533 buffer.set_language(Some(language.clone()), cx);
534 });
535
536 // For local worktrees, start a language server if needed.
537 let worktree = worktree.read(cx);
538 let worktree_id = worktree.id();
539 let worktree_abs_path = worktree.as_local()?.abs_path().clone();
540 let language_server = match self
541 .language_servers
542 .entry((worktree_id, language.name().to_string()))
543 {
544 hash_map::Entry::Occupied(e) => Some(e.get().clone()),
545 hash_map::Entry::Vacant(e) => {
546 Self::start_language_server(self.client.clone(), language, &worktree_abs_path, cx)
547 .map(|server| e.insert(server).clone())
548 }
549 };
550
551 buffer.update(cx, |buffer, cx| {
552 buffer.set_language_server(language_server, cx)
553 });
554
555 None
556 }
557
558 fn start_language_server(
559 rpc: Arc<Client>,
560 language: Arc<Language>,
561 worktree_path: &Path,
562 cx: &mut ModelContext<Self>,
563 ) -> Option<Arc<LanguageServer>> {
564 enum LspEvent {
565 DiagnosticsStart,
566 DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
567 DiagnosticsFinish,
568 }
569
570 let language_server = language
571 .start_server(worktree_path, cx)
572 .log_err()
573 .flatten()?;
574 let disk_based_sources = language
575 .disk_based_diagnostic_sources()
576 .cloned()
577 .unwrap_or_default();
578 let disk_based_diagnostics_progress_token =
579 language.disk_based_diagnostics_progress_token().cloned();
580 let has_disk_based_diagnostic_progress_token =
581 disk_based_diagnostics_progress_token.is_some();
582 let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
583
584 // Listen for `PublishDiagnostics` notifications.
585 language_server
586 .on_notification::<lsp::notification::PublishDiagnostics, _>({
587 let diagnostics_tx = diagnostics_tx.clone();
588 move |params| {
589 if !has_disk_based_diagnostic_progress_token {
590 block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
591 }
592 block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params))).ok();
593 if !has_disk_based_diagnostic_progress_token {
594 block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
595 }
596 }
597 })
598 .detach();
599
600 // Listen for `Progress` notifications. Send an event when the language server
601 // transitions between running jobs and not running any jobs.
602 let mut running_jobs_for_this_server: i32 = 0;
603 language_server
604 .on_notification::<lsp::notification::Progress, _>(move |params| {
605 let token = match params.token {
606 lsp::NumberOrString::Number(_) => None,
607 lsp::NumberOrString::String(token) => Some(token),
608 };
609
610 if token == disk_based_diagnostics_progress_token {
611 match params.value {
612 lsp::ProgressParamsValue::WorkDone(progress) => match progress {
613 lsp::WorkDoneProgress::Begin(_) => {
614 running_jobs_for_this_server += 1;
615 if running_jobs_for_this_server == 1 {
616 block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
617 }
618 }
619 lsp::WorkDoneProgress::End(_) => {
620 running_jobs_for_this_server -= 1;
621 if running_jobs_for_this_server == 0 {
622 block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
623 }
624 }
625 _ => {}
626 },
627 }
628 }
629 })
630 .detach();
631
632 // Process all the LSP events.
633 cx.spawn_weak(|this, mut cx| async move {
634 while let Ok(message) = diagnostics_rx.recv().await {
635 let this = cx.read(|cx| this.upgrade(cx))?;
636 match message {
637 LspEvent::DiagnosticsStart => {
638 let send = this.update(&mut cx, |this, cx| {
639 this.disk_based_diagnostics_started(cx);
640 this.remote_id().map(|project_id| {
641 rpc.send(proto::DiskBasedDiagnosticsUpdating { project_id })
642 })
643 });
644 if let Some(send) = send {
645 send.await.log_err();
646 }
647 }
648 LspEvent::DiagnosticsUpdate(params) => {
649 this.update(&mut cx, |this, cx| {
650 this.update_diagnostics(params, &disk_based_sources, cx)
651 .log_err();
652 });
653 }
654 LspEvent::DiagnosticsFinish => {
655 let send = this.update(&mut cx, |this, cx| {
656 this.disk_based_diagnostics_finished(cx);
657 this.remote_id().map(|project_id| {
658 rpc.send(proto::DiskBasedDiagnosticsUpdated { project_id })
659 })
660 });
661 if let Some(send) = send {
662 send.await.log_err();
663 }
664 }
665 }
666 }
667 Some(())
668 })
669 .detach();
670
671 Some(language_server)
672 }
673
674 fn update_diagnostics(
675 &mut self,
676 diagnostics: lsp::PublishDiagnosticsParams,
677 disk_based_sources: &HashSet<String>,
678 cx: &mut ModelContext<Self>,
679 ) -> Result<()> {
680 let path = diagnostics
681 .uri
682 .to_file_path()
683 .map_err(|_| anyhow!("URI is not a file"))?;
684 let (worktree, relative_path) = self
685 .find_worktree_for_abs_path(&path, cx)
686 .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?;
687 let project_path = ProjectPath {
688 worktree_id: worktree.read(cx).id(),
689 path: relative_path.into(),
690 };
691 worktree.update(cx, |worktree, cx| {
692 worktree.as_local_mut().unwrap().update_diagnostics(
693 project_path.path.clone(),
694 diagnostics,
695 disk_based_sources,
696 cx,
697 )
698 })?;
699 cx.emit(Event::DiagnosticsUpdated(project_path));
700 Ok(())
701 }
702
703 pub fn definition<T: ToOffset>(
704 &self,
705 source_buffer_handle: &ModelHandle<Buffer>,
706 position: T,
707 cx: &mut ModelContext<Self>,
708 ) -> Task<Result<Vec<Definition>>> {
709 let source_buffer_handle = source_buffer_handle.clone();
710 let buffer = source_buffer_handle.read(cx);
711 let worktree;
712 let buffer_abs_path;
713 if let Some(file) = File::from_dyn(buffer.file()) {
714 worktree = file.worktree.clone();
715 buffer_abs_path = file.abs_path();
716 } else {
717 return Task::ready(Err(anyhow!("buffer does not belong to any worktree")));
718 };
719
720 if worktree.read(cx).as_local().is_some() {
721 let point = buffer.offset_to_point_utf16(position.to_offset(buffer));
722 let buffer_abs_path = buffer_abs_path.unwrap();
723 let lang_name;
724 let lang_server;
725 if let Some(lang) = buffer.language() {
726 lang_name = lang.name().to_string();
727 if let Some(server) = self
728 .language_servers
729 .get(&(worktree.read(cx).id(), lang_name.clone()))
730 {
731 lang_server = server.clone();
732 } else {
733 return Task::ready(Err(anyhow!("buffer does not have a language server")));
734 };
735 } else {
736 return Task::ready(Err(anyhow!("buffer does not have a language")));
737 }
738
739 cx.spawn(|this, mut cx| async move {
740 let response = lang_server
741 .request::<lsp::request::GotoDefinition>(lsp::GotoDefinitionParams {
742 text_document_position_params: lsp::TextDocumentPositionParams {
743 text_document: lsp::TextDocumentIdentifier::new(
744 lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
745 ),
746 position: lsp::Position::new(point.row, point.column),
747 },
748 work_done_progress_params: Default::default(),
749 partial_result_params: Default::default(),
750 })
751 .await?;
752
753 let mut definitions = Vec::new();
754 if let Some(response) = response {
755 let mut unresolved_locations = Vec::new();
756 match response {
757 lsp::GotoDefinitionResponse::Scalar(loc) => {
758 unresolved_locations.push((loc.uri, loc.range));
759 }
760 lsp::GotoDefinitionResponse::Array(locs) => {
761 unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range)));
762 }
763 lsp::GotoDefinitionResponse::Link(links) => {
764 unresolved_locations.extend(
765 links
766 .into_iter()
767 .map(|l| (l.target_uri, l.target_selection_range)),
768 );
769 }
770 }
771
772 for (target_uri, target_range) in unresolved_locations {
773 let abs_path = target_uri
774 .to_file_path()
775 .map_err(|_| anyhow!("invalid target path"))?;
776
777 let (worktree, relative_path) = if let Some(result) = this
778 .read_with(&cx, |this, cx| {
779 this.find_worktree_for_abs_path(&abs_path, cx)
780 }) {
781 result
782 } else {
783 let (worktree, relative_path) = this
784 .update(&mut cx, |this, cx| {
785 this.create_worktree_for_abs_path(&abs_path, true, cx)
786 })
787 .await?;
788 this.update(&mut cx, |this, cx| {
789 this.language_servers.insert(
790 (worktree.read(cx).id(), lang_name.clone()),
791 lang_server.clone(),
792 );
793 });
794 (worktree, relative_path)
795 };
796
797 let project_path = ProjectPath {
798 worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
799 path: relative_path.into(),
800 };
801 let target_buffer_handle = this
802 .update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
803 .await?;
804 cx.read(|cx| {
805 let target_buffer = target_buffer_handle.read(cx);
806 let target_start = target_buffer
807 .clip_point_utf16(target_range.start.to_point_utf16(), Bias::Left);
808 let target_end = target_buffer
809 .clip_point_utf16(target_range.end.to_point_utf16(), Bias::Left);
810 definitions.push(Definition {
811 target_buffer: target_buffer_handle,
812 target_range: target_buffer.anchor_after(target_start)
813 ..target_buffer.anchor_before(target_end),
814 });
815 });
816 }
817 }
818
819 Ok(definitions)
820 })
821 } else {
822 log::info!("go to definition is not yet implemented for guests");
823 Task::ready(Ok(Default::default()))
824 }
825 }
826
827 pub fn find_or_create_worktree_for_abs_path(
828 &self,
829 abs_path: impl AsRef<Path>,
830 weak: bool,
831 cx: &mut ModelContext<Self>,
832 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
833 let abs_path = abs_path.as_ref();
834 if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) {
835 Task::ready(Ok((tree.clone(), relative_path.into())))
836 } else {
837 self.create_worktree_for_abs_path(abs_path, weak, cx)
838 }
839 }
840
841 fn create_worktree_for_abs_path(
842 &self,
843 abs_path: &Path,
844 weak: bool,
845 cx: &mut ModelContext<Self>,
846 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
847 let worktree = self.add_local_worktree(abs_path, weak, cx);
848 cx.background().spawn(async move {
849 let worktree = worktree.await?;
850 Ok((worktree, PathBuf::new()))
851 })
852 }
853
854 fn find_worktree_for_abs_path(
855 &self,
856 abs_path: &Path,
857 cx: &AppContext,
858 ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
859 for tree in self.worktrees(cx) {
860 if let Some(relative_path) = tree
861 .read(cx)
862 .as_local()
863 .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
864 {
865 return Some((tree.clone(), relative_path.into()));
866 }
867 }
868 None
869 }
870
871 pub fn is_shared(&self) -> bool {
872 match &self.client_state {
873 ProjectClientState::Local { is_shared, .. } => *is_shared,
874 ProjectClientState::Remote { .. } => false,
875 }
876 }
877
878 fn add_local_worktree(
879 &self,
880 abs_path: impl AsRef<Path>,
881 weak: bool,
882 cx: &mut ModelContext<Self>,
883 ) -> Task<Result<ModelHandle<Worktree>>> {
884 let fs = self.fs.clone();
885 let client = self.client.clone();
886 let user_store = self.user_store.clone();
887 let path = Arc::from(abs_path.as_ref());
888 cx.spawn(|project, mut cx| async move {
889 let worktree =
890 Worktree::open_local(client.clone(), user_store, path, weak, fs, &mut cx).await?;
891
892 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
893 project.add_worktree(&worktree, cx);
894 (project.remote_id(), project.is_shared())
895 });
896
897 if let Some(project_id) = remote_project_id {
898 worktree
899 .update(&mut cx, |worktree, cx| {
900 worktree.as_local_mut().unwrap().register(project_id, cx)
901 })
902 .await?;
903 if is_shared {
904 worktree
905 .update(&mut cx, |worktree, cx| {
906 worktree.as_local_mut().unwrap().share(cx)
907 })
908 .await?;
909 }
910 }
911
912 Ok(worktree)
913 })
914 }
915
916 pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext<Self>) {
917 self.worktrees.retain(|worktree| {
918 worktree
919 .upgrade(cx)
920 .map_or(false, |w| w.read(cx).id() != id)
921 });
922 cx.notify();
923 }
924
925 fn add_worktree(&mut self, worktree: &ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
926 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
927
928 let push_weak_handle = {
929 let worktree = worktree.read(cx);
930 worktree.is_local() && worktree.is_weak()
931 };
932 if push_weak_handle {
933 cx.observe_release(&worktree, |this, cx| {
934 this.worktrees
935 .retain(|worktree| worktree.upgrade(cx).is_some());
936 cx.notify();
937 })
938 .detach();
939 self.worktrees
940 .push(WorktreeHandle::Weak(worktree.downgrade()));
941 } else {
942 self.worktrees
943 .push(WorktreeHandle::Strong(worktree.clone()));
944 }
945 cx.notify();
946 }
947
948 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
949 let new_active_entry = entry.and_then(|project_path| {
950 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
951 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
952 Some(ProjectEntry {
953 worktree_id: project_path.worktree_id,
954 entry_id: entry.id,
955 })
956 });
957 if new_active_entry != self.active_entry {
958 self.active_entry = new_active_entry;
959 cx.emit(Event::ActiveEntryChanged(new_active_entry));
960 }
961 }
962
963 pub fn is_running_disk_based_diagnostics(&self) -> bool {
964 self.language_servers_with_diagnostics_running > 0
965 }
966
967 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
968 let mut summary = DiagnosticSummary::default();
969 for (_, path_summary) in self.diagnostic_summaries(cx) {
970 summary.error_count += path_summary.error_count;
971 summary.warning_count += path_summary.warning_count;
972 summary.info_count += path_summary.info_count;
973 summary.hint_count += path_summary.hint_count;
974 }
975 summary
976 }
977
978 pub fn diagnostic_summaries<'a>(
979 &'a self,
980 cx: &'a AppContext,
981 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
982 self.worktrees(cx).flat_map(move |worktree| {
983 let worktree = worktree.read(cx);
984 let worktree_id = worktree.id();
985 worktree
986 .diagnostic_summaries()
987 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
988 })
989 }
990
991 fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
992 self.language_servers_with_diagnostics_running += 1;
993 if self.language_servers_with_diagnostics_running == 1 {
994 cx.emit(Event::DiskBasedDiagnosticsStarted);
995 }
996 }
997
998 fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
999 cx.emit(Event::DiskBasedDiagnosticsUpdated);
1000 self.language_servers_with_diagnostics_running -= 1;
1001 if self.language_servers_with_diagnostics_running == 0 {
1002 cx.emit(Event::DiskBasedDiagnosticsFinished);
1003 }
1004 }
1005
1006 pub fn active_entry(&self) -> Option<ProjectEntry> {
1007 self.active_entry
1008 }
1009
1010 // RPC message handlers
1011
1012 fn handle_unshare_project(
1013 &mut self,
1014 _: TypedEnvelope<proto::UnshareProject>,
1015 _: Arc<Client>,
1016 cx: &mut ModelContext<Self>,
1017 ) -> Result<()> {
1018 if let ProjectClientState::Remote {
1019 sharing_has_stopped,
1020 ..
1021 } = &mut self.client_state
1022 {
1023 *sharing_has_stopped = true;
1024 self.collaborators.clear();
1025 cx.notify();
1026 Ok(())
1027 } else {
1028 unreachable!()
1029 }
1030 }
1031
1032 fn handle_add_collaborator(
1033 &mut self,
1034 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
1035 _: Arc<Client>,
1036 cx: &mut ModelContext<Self>,
1037 ) -> Result<()> {
1038 let user_store = self.user_store.clone();
1039 let collaborator = envelope
1040 .payload
1041 .collaborator
1042 .take()
1043 .ok_or_else(|| anyhow!("empty collaborator"))?;
1044
1045 cx.spawn(|this, mut cx| {
1046 async move {
1047 let collaborator =
1048 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
1049 this.update(&mut cx, |this, cx| {
1050 this.collaborators
1051 .insert(collaborator.peer_id, collaborator);
1052 cx.notify();
1053 });
1054 Ok(())
1055 }
1056 .log_err()
1057 })
1058 .detach();
1059
1060 Ok(())
1061 }
1062
1063 fn handle_remove_collaborator(
1064 &mut self,
1065 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
1066 _: Arc<Client>,
1067 cx: &mut ModelContext<Self>,
1068 ) -> Result<()> {
1069 let peer_id = PeerId(envelope.payload.peer_id);
1070 let replica_id = self
1071 .collaborators
1072 .remove(&peer_id)
1073 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
1074 .replica_id;
1075 for worktree in self.worktrees(cx).collect::<Vec<_>>() {
1076 worktree.update(cx, |worktree, cx| {
1077 worktree.remove_collaborator(peer_id, replica_id, cx);
1078 })
1079 }
1080 Ok(())
1081 }
1082
1083 fn handle_share_worktree(
1084 &mut self,
1085 envelope: TypedEnvelope<proto::ShareWorktree>,
1086 client: Arc<Client>,
1087 cx: &mut ModelContext<Self>,
1088 ) -> Result<()> {
1089 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
1090 let replica_id = self.replica_id();
1091 let worktree = envelope
1092 .payload
1093 .worktree
1094 .ok_or_else(|| anyhow!("invalid worktree"))?;
1095 let user_store = self.user_store.clone();
1096 cx.spawn(|this, mut cx| {
1097 async move {
1098 let worktree =
1099 Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx)
1100 .await?;
1101 this.update(&mut cx, |this, cx| this.add_worktree(&worktree, cx));
1102 Ok(())
1103 }
1104 .log_err()
1105 })
1106 .detach();
1107 Ok(())
1108 }
1109
1110 fn handle_unregister_worktree(
1111 &mut self,
1112 envelope: TypedEnvelope<proto::UnregisterWorktree>,
1113 _: Arc<Client>,
1114 cx: &mut ModelContext<Self>,
1115 ) -> Result<()> {
1116 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1117 self.remove_worktree(worktree_id, cx);
1118 Ok(())
1119 }
1120
1121 fn handle_update_worktree(
1122 &mut self,
1123 envelope: TypedEnvelope<proto::UpdateWorktree>,
1124 _: Arc<Client>,
1125 cx: &mut ModelContext<Self>,
1126 ) -> Result<()> {
1127 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1128 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1129 worktree.update(cx, |worktree, cx| {
1130 let worktree = worktree.as_remote_mut().unwrap();
1131 worktree.update_from_remote(envelope, cx)
1132 })?;
1133 }
1134 Ok(())
1135 }
1136
1137 fn handle_update_diagnostic_summary(
1138 &mut self,
1139 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
1140 _: Arc<Client>,
1141 cx: &mut ModelContext<Self>,
1142 ) -> Result<()> {
1143 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1144 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1145 if let Some(summary) = envelope.payload.summary {
1146 let project_path = ProjectPath {
1147 worktree_id,
1148 path: Path::new(&summary.path).into(),
1149 };
1150 worktree.update(cx, |worktree, _| {
1151 worktree
1152 .as_remote_mut()
1153 .unwrap()
1154 .update_diagnostic_summary(project_path.path.clone(), &summary);
1155 });
1156 cx.emit(Event::DiagnosticsUpdated(project_path));
1157 }
1158 }
1159 Ok(())
1160 }
1161
1162 fn handle_disk_based_diagnostics_updating(
1163 &mut self,
1164 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
1165 _: Arc<Client>,
1166 cx: &mut ModelContext<Self>,
1167 ) -> Result<()> {
1168 self.disk_based_diagnostics_started(cx);
1169 Ok(())
1170 }
1171
1172 fn handle_disk_based_diagnostics_updated(
1173 &mut self,
1174 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
1175 _: Arc<Client>,
1176 cx: &mut ModelContext<Self>,
1177 ) -> Result<()> {
1178 self.disk_based_diagnostics_finished(cx);
1179 Ok(())
1180 }
1181
1182 pub fn handle_update_buffer(
1183 &mut self,
1184 envelope: TypedEnvelope<proto::UpdateBuffer>,
1185 _: Arc<Client>,
1186 cx: &mut ModelContext<Self>,
1187 ) -> Result<()> {
1188 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1189 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1190 worktree.update(cx, |worktree, cx| {
1191 worktree.handle_update_buffer(envelope, cx)
1192 })?;
1193 }
1194 Ok(())
1195 }
1196
1197 pub fn handle_save_buffer(
1198 &mut self,
1199 envelope: TypedEnvelope<proto::SaveBuffer>,
1200 rpc: Arc<Client>,
1201 cx: &mut ModelContext<Self>,
1202 ) -> Result<()> {
1203 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1204 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1205 worktree.update(cx, |worktree, cx| {
1206 worktree.handle_save_buffer(envelope, rpc, cx)
1207 })?;
1208 }
1209 Ok(())
1210 }
1211
1212 pub fn handle_format_buffer(
1213 &mut self,
1214 envelope: TypedEnvelope<proto::FormatBuffer>,
1215 rpc: Arc<Client>,
1216 cx: &mut ModelContext<Self>,
1217 ) -> Result<()> {
1218 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1219 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1220 worktree.update(cx, |worktree, cx| {
1221 worktree.handle_format_buffer(envelope, rpc, cx)
1222 })?;
1223 }
1224 Ok(())
1225 }
1226
1227 pub fn handle_open_buffer(
1228 &mut self,
1229 envelope: TypedEnvelope<proto::OpenBuffer>,
1230 rpc: Arc<Client>,
1231 cx: &mut ModelContext<Self>,
1232 ) -> anyhow::Result<()> {
1233 let receipt = envelope.receipt();
1234 let peer_id = envelope.original_sender_id()?;
1235 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1236 let worktree = self
1237 .worktree_for_id(worktree_id, cx)
1238 .ok_or_else(|| anyhow!("no such worktree"))?;
1239
1240 let task = self.open_buffer(
1241 ProjectPath {
1242 worktree_id,
1243 path: PathBuf::from(envelope.payload.path).into(),
1244 },
1245 cx,
1246 );
1247 cx.spawn(|_, mut cx| {
1248 async move {
1249 let buffer = task.await?;
1250 let response = worktree.update(&mut cx, |worktree, cx| {
1251 worktree
1252 .as_local_mut()
1253 .unwrap()
1254 .open_remote_buffer(peer_id, buffer, cx)
1255 });
1256 rpc.respond(receipt, response).await?;
1257 Ok(())
1258 }
1259 .log_err()
1260 })
1261 .detach();
1262 Ok(())
1263 }
1264
1265 pub fn handle_close_buffer(
1266 &mut self,
1267 envelope: TypedEnvelope<proto::CloseBuffer>,
1268 _: Arc<Client>,
1269 cx: &mut ModelContext<Self>,
1270 ) -> anyhow::Result<()> {
1271 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1272 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1273 worktree.update(cx, |worktree, cx| {
1274 worktree
1275 .as_local_mut()
1276 .unwrap()
1277 .close_remote_buffer(envelope, cx)
1278 })?;
1279 }
1280 Ok(())
1281 }
1282
1283 pub fn handle_buffer_saved(
1284 &mut self,
1285 envelope: TypedEnvelope<proto::BufferSaved>,
1286 _: Arc<Client>,
1287 cx: &mut ModelContext<Self>,
1288 ) -> Result<()> {
1289 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1290 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1291 worktree.update(cx, |worktree, cx| {
1292 worktree.handle_buffer_saved(envelope, cx)
1293 })?;
1294 }
1295 Ok(())
1296 }
1297
1298 pub fn match_paths<'a>(
1299 &self,
1300 query: &'a str,
1301 include_ignored: bool,
1302 smart_case: bool,
1303 max_results: usize,
1304 cancel_flag: &'a AtomicBool,
1305 cx: &AppContext,
1306 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
1307 let worktrees = self
1308 .worktrees(cx)
1309 .filter(|worktree| !worktree.read(cx).is_weak())
1310 .collect::<Vec<_>>();
1311 let include_root_name = worktrees.len() > 1;
1312 let candidate_sets = worktrees
1313 .into_iter()
1314 .map(|worktree| CandidateSet {
1315 snapshot: worktree.read(cx).snapshot(),
1316 include_ignored,
1317 include_root_name,
1318 })
1319 .collect::<Vec<_>>();
1320
1321 let background = cx.background().clone();
1322 async move {
1323 fuzzy::match_paths(
1324 candidate_sets.as_slice(),
1325 query,
1326 smart_case,
1327 max_results,
1328 cancel_flag,
1329 background,
1330 )
1331 .await
1332 }
1333 }
1334}
1335
1336impl WorktreeHandle {
1337 pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
1338 match self {
1339 WorktreeHandle::Strong(handle) => Some(handle.clone()),
1340 WorktreeHandle::Weak(handle) => handle.upgrade(cx),
1341 }
1342 }
1343}
1344
1345struct CandidateSet {
1346 snapshot: Snapshot,
1347 include_ignored: bool,
1348 include_root_name: bool,
1349}
1350
1351impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
1352 type Candidates = CandidateSetIter<'a>;
1353
1354 fn id(&self) -> usize {
1355 self.snapshot.id().to_usize()
1356 }
1357
1358 fn len(&self) -> usize {
1359 if self.include_ignored {
1360 self.snapshot.file_count()
1361 } else {
1362 self.snapshot.visible_file_count()
1363 }
1364 }
1365
1366 fn prefix(&self) -> Arc<str> {
1367 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
1368 self.snapshot.root_name().into()
1369 } else if self.include_root_name {
1370 format!("{}/", self.snapshot.root_name()).into()
1371 } else {
1372 "".into()
1373 }
1374 }
1375
1376 fn candidates(&'a self, start: usize) -> Self::Candidates {
1377 CandidateSetIter {
1378 traversal: self.snapshot.files(self.include_ignored, start),
1379 }
1380 }
1381}
1382
1383struct CandidateSetIter<'a> {
1384 traversal: Traversal<'a>,
1385}
1386
1387impl<'a> Iterator for CandidateSetIter<'a> {
1388 type Item = PathMatchCandidate<'a>;
1389
1390 fn next(&mut self) -> Option<Self::Item> {
1391 self.traversal.next().map(|entry| {
1392 if let EntryKind::File(char_bag) = entry.kind {
1393 PathMatchCandidate {
1394 path: &entry.path,
1395 char_bag,
1396 }
1397 } else {
1398 unreachable!()
1399 }
1400 })
1401 }
1402}
1403
1404impl Entity for Project {
1405 type Event = Event;
1406
1407 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
1408 match &self.client_state {
1409 ProjectClientState::Local { remote_id_rx, .. } => {
1410 if let Some(project_id) = *remote_id_rx.borrow() {
1411 let rpc = self.client.clone();
1412 cx.spawn(|_| async move {
1413 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
1414 log::error!("error unregistering project: {}", err);
1415 }
1416 })
1417 .detach();
1418 }
1419 }
1420 ProjectClientState::Remote { remote_id, .. } => {
1421 let rpc = self.client.clone();
1422 let project_id = *remote_id;
1423 cx.spawn(|_| async move {
1424 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
1425 log::error!("error leaving project: {}", err);
1426 }
1427 })
1428 .detach();
1429 }
1430 }
1431 }
1432
1433 fn app_will_quit(
1434 &mut self,
1435 _: &mut MutableAppContext,
1436 ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
1437 use futures::FutureExt;
1438
1439 let shutdown_futures = self
1440 .language_servers
1441 .drain()
1442 .filter_map(|(_, server)| server.shutdown())
1443 .collect::<Vec<_>>();
1444 Some(
1445 async move {
1446 futures::future::join_all(shutdown_futures).await;
1447 }
1448 .boxed(),
1449 )
1450 }
1451}
1452
1453impl Collaborator {
1454 fn from_proto(
1455 message: proto::Collaborator,
1456 user_store: &ModelHandle<UserStore>,
1457 cx: &mut AsyncAppContext,
1458 ) -> impl Future<Output = Result<Self>> {
1459 let user = user_store.update(cx, |user_store, cx| {
1460 user_store.fetch_user(message.user_id, cx)
1461 });
1462
1463 async move {
1464 Ok(Self {
1465 peer_id: PeerId(message.peer_id),
1466 user: user.await?,
1467 replica_id: message.replica_id as ReplicaId,
1468 })
1469 }
1470 }
1471}
1472
1473#[cfg(test)]
1474mod tests {
1475 use super::{Event, *};
1476 use client::test::FakeHttpClient;
1477 use fs::RealFs;
1478 use futures::StreamExt;
1479 use gpui::{test::subscribe, TestAppContext};
1480 use language::{
1481 tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageRegistry,
1482 LanguageServerConfig, Point,
1483 };
1484 use lsp::Url;
1485 use serde_json::json;
1486 use std::{os::unix, path::PathBuf};
1487 use util::test::temp_tree;
1488
1489 #[gpui::test]
1490 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1491 let dir = temp_tree(json!({
1492 "root": {
1493 "apple": "",
1494 "banana": {
1495 "carrot": {
1496 "date": "",
1497 "endive": "",
1498 }
1499 },
1500 "fennel": {
1501 "grape": "",
1502 }
1503 }
1504 }));
1505
1506 let root_link_path = dir.path().join("root_link");
1507 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1508 unix::fs::symlink(
1509 &dir.path().join("root/fennel"),
1510 &dir.path().join("root/finnochio"),
1511 )
1512 .unwrap();
1513
1514 let project = build_project(&mut cx);
1515
1516 let (tree, _) = project
1517 .update(&mut cx, |project, cx| {
1518 project.find_or_create_worktree_for_abs_path(&root_link_path, false, cx)
1519 })
1520 .await
1521 .unwrap();
1522
1523 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1524 .await;
1525 cx.read(|cx| {
1526 let tree = tree.read(cx);
1527 assert_eq!(tree.file_count(), 5);
1528 assert_eq!(
1529 tree.inode_for_path("fennel/grape"),
1530 tree.inode_for_path("finnochio/grape")
1531 );
1532 });
1533
1534 let cancel_flag = Default::default();
1535 let results = project
1536 .read_with(&cx, |project, cx| {
1537 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1538 })
1539 .await;
1540 assert_eq!(
1541 results
1542 .into_iter()
1543 .map(|result| result.path)
1544 .collect::<Vec<Arc<Path>>>(),
1545 vec![
1546 PathBuf::from("banana/carrot/date").into(),
1547 PathBuf::from("banana/carrot/endive").into(),
1548 ]
1549 );
1550 }
1551
1552 #[gpui::test]
1553 async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
1554 let (language_server_config, mut fake_server) =
1555 LanguageServerConfig::fake(cx.background()).await;
1556 let progress_token = language_server_config
1557 .disk_based_diagnostics_progress_token
1558 .clone()
1559 .unwrap();
1560
1561 let mut languages = LanguageRegistry::new();
1562 languages.add(Arc::new(Language::new(
1563 LanguageConfig {
1564 name: "Rust".to_string(),
1565 path_suffixes: vec!["rs".to_string()],
1566 language_server: Some(language_server_config),
1567 ..Default::default()
1568 },
1569 Some(tree_sitter_rust::language()),
1570 )));
1571
1572 let dir = temp_tree(json!({
1573 "a.rs": "fn a() { A }",
1574 "b.rs": "const y: i32 = 1",
1575 }));
1576
1577 let http_client = FakeHttpClient::with_404_response();
1578 let client = Client::new(http_client.clone());
1579 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1580
1581 let project = cx.update(|cx| {
1582 Project::local(
1583 client,
1584 user_store,
1585 Arc::new(languages),
1586 Arc::new(RealFs),
1587 cx,
1588 )
1589 });
1590
1591 let (tree, _) = project
1592 .update(&mut cx, |project, cx| {
1593 project.find_or_create_worktree_for_abs_path(dir.path(), false, cx)
1594 })
1595 .await
1596 .unwrap();
1597 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
1598
1599 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1600 .await;
1601
1602 // Cause worktree to start the fake language server
1603 let _buffer = project
1604 .update(&mut cx, |project, cx| {
1605 project.open_buffer(
1606 ProjectPath {
1607 worktree_id,
1608 path: Path::new("b.rs").into(),
1609 },
1610 cx,
1611 )
1612 })
1613 .await
1614 .unwrap();
1615
1616 let mut events = subscribe(&project, &mut cx);
1617
1618 fake_server.start_progress(&progress_token).await;
1619 assert_eq!(
1620 events.next().await.unwrap(),
1621 Event::DiskBasedDiagnosticsStarted
1622 );
1623
1624 fake_server.start_progress(&progress_token).await;
1625 fake_server.end_progress(&progress_token).await;
1626 fake_server.start_progress(&progress_token).await;
1627
1628 fake_server
1629 .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
1630 uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
1631 version: None,
1632 diagnostics: vec![lsp::Diagnostic {
1633 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1634 severity: Some(lsp::DiagnosticSeverity::ERROR),
1635 message: "undefined variable 'A'".to_string(),
1636 ..Default::default()
1637 }],
1638 })
1639 .await;
1640 assert_eq!(
1641 events.next().await.unwrap(),
1642 Event::DiagnosticsUpdated(ProjectPath {
1643 worktree_id,
1644 path: Arc::from(Path::new("a.rs"))
1645 })
1646 );
1647
1648 fake_server.end_progress(&progress_token).await;
1649 fake_server.end_progress(&progress_token).await;
1650 assert_eq!(
1651 events.next().await.unwrap(),
1652 Event::DiskBasedDiagnosticsUpdated
1653 );
1654 assert_eq!(
1655 events.next().await.unwrap(),
1656 Event::DiskBasedDiagnosticsFinished
1657 );
1658
1659 let (buffer, _) = tree
1660 .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx))
1661 .await
1662 .unwrap();
1663
1664 buffer.read_with(&cx, |buffer, _| {
1665 let snapshot = buffer.snapshot();
1666 let diagnostics = snapshot
1667 .diagnostics_in_range::<_, Point>(0..buffer.len())
1668 .collect::<Vec<_>>();
1669 assert_eq!(
1670 diagnostics,
1671 &[DiagnosticEntry {
1672 range: Point::new(0, 9)..Point::new(0, 10),
1673 diagnostic: Diagnostic {
1674 severity: lsp::DiagnosticSeverity::ERROR,
1675 message: "undefined variable 'A'".to_string(),
1676 group_id: 0,
1677 is_primary: true,
1678 ..Default::default()
1679 }
1680 }]
1681 )
1682 });
1683 }
1684
1685 #[gpui::test]
1686 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1687 let dir = temp_tree(json!({
1688 "root": {
1689 "dir1": {},
1690 "dir2": {
1691 "dir3": {}
1692 }
1693 }
1694 }));
1695
1696 let project = build_project(&mut cx);
1697 let (tree, _) = project
1698 .update(&mut cx, |project, cx| {
1699 project.find_or_create_worktree_for_abs_path(&dir.path(), false, cx)
1700 })
1701 .await
1702 .unwrap();
1703
1704 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1705 .await;
1706
1707 let cancel_flag = Default::default();
1708 let results = project
1709 .read_with(&cx, |project, cx| {
1710 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1711 })
1712 .await;
1713
1714 assert!(results.is_empty());
1715 }
1716
1717 #[gpui::test]
1718 async fn test_definition(mut cx: gpui::TestAppContext) {
1719 let (language_server_config, mut fake_server) =
1720 LanguageServerConfig::fake(cx.background()).await;
1721
1722 let mut languages = LanguageRegistry::new();
1723 languages.add(Arc::new(Language::new(
1724 LanguageConfig {
1725 name: "Rust".to_string(),
1726 path_suffixes: vec!["rs".to_string()],
1727 language_server: Some(language_server_config),
1728 ..Default::default()
1729 },
1730 Some(tree_sitter_rust::language()),
1731 )));
1732
1733 let dir = temp_tree(json!({
1734 "a.rs": "const fn a() { A }",
1735 "b.rs": "const y: i32 = crate::a()",
1736 }));
1737
1738 let http_client = FakeHttpClient::with_404_response();
1739 let client = Client::new(http_client.clone());
1740 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1741 let project = cx.update(|cx| {
1742 Project::local(
1743 client,
1744 user_store,
1745 Arc::new(languages),
1746 Arc::new(RealFs),
1747 cx,
1748 )
1749 });
1750
1751 let (tree, _) = project
1752 .update(&mut cx, |project, cx| {
1753 project.find_or_create_worktree_for_abs_path(dir.path().join("b.rs"), false, cx)
1754 })
1755 .await
1756 .unwrap();
1757 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
1758 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1759 .await;
1760
1761 // Cause worktree to start the fake language server
1762 let buffer = project
1763 .update(&mut cx, |project, cx| {
1764 project.open_buffer(
1765 ProjectPath {
1766 worktree_id,
1767 path: Path::new("").into(),
1768 },
1769 cx,
1770 )
1771 })
1772 .await
1773 .unwrap();
1774 let definitions =
1775 project.update(&mut cx, |project, cx| project.definition(&buffer, 22, cx));
1776 let (request_id, request) = fake_server
1777 .receive_request::<lsp::request::GotoDefinition>()
1778 .await;
1779 let request_params = request.text_document_position_params;
1780 assert_eq!(
1781 request_params.text_document.uri.to_file_path().unwrap(),
1782 dir.path().join("b.rs")
1783 );
1784 assert_eq!(request_params.position, lsp::Position::new(0, 22));
1785
1786 fake_server
1787 .respond(
1788 request_id,
1789 Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
1790 lsp::Url::from_file_path(dir.path().join("a.rs")).unwrap(),
1791 lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1792 ))),
1793 )
1794 .await;
1795 let mut definitions = definitions.await.unwrap();
1796 assert_eq!(definitions.len(), 1);
1797 let definition = definitions.pop().unwrap();
1798 cx.update(|cx| {
1799 let target_buffer = definition.target_buffer.read(cx);
1800 assert_eq!(
1801 target_buffer.file().unwrap().abs_path(),
1802 Some(dir.path().join("a.rs"))
1803 );
1804 assert_eq!(definition.target_range.to_offset(target_buffer), 9..10);
1805 assert_eq!(
1806 list_worktrees(&project, cx),
1807 [
1808 (dir.path().join("b.rs"), false),
1809 (dir.path().join("a.rs"), true)
1810 ]
1811 );
1812
1813 drop(definition);
1814 });
1815 cx.read(|cx| {
1816 assert_eq!(
1817 list_worktrees(&project, cx),
1818 [(dir.path().join("b.rs"), false)]
1819 );
1820 });
1821
1822 fn list_worktrees(project: &ModelHandle<Project>, cx: &AppContext) -> Vec<(PathBuf, bool)> {
1823 project
1824 .read(cx)
1825 .worktrees(cx)
1826 .map(|worktree| {
1827 let worktree = worktree.read(cx);
1828 (
1829 worktree.as_local().unwrap().abs_path().to_path_buf(),
1830 worktree.is_weak(),
1831 )
1832 })
1833 .collect::<Vec<_>>()
1834 }
1835 }
1836
1837 fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1838 let languages = Arc::new(LanguageRegistry::new());
1839 let fs = Arc::new(RealFs);
1840 let http_client = FakeHttpClient::with_404_response();
1841 let client = client::Client::new(http_client.clone());
1842 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1843 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1844 }
1845}