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 todo!()
823 }
824 }
825
826 pub fn find_or_create_worktree_for_abs_path(
827 &self,
828 abs_path: impl AsRef<Path>,
829 weak: bool,
830 cx: &mut ModelContext<Self>,
831 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
832 let abs_path = abs_path.as_ref();
833 if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) {
834 Task::ready(Ok((tree.clone(), relative_path.into())))
835 } else {
836 self.create_worktree_for_abs_path(abs_path, weak, cx)
837 }
838 }
839
840 fn create_worktree_for_abs_path(
841 &self,
842 abs_path: &Path,
843 weak: bool,
844 cx: &mut ModelContext<Self>,
845 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
846 let worktree = self.add_local_worktree(abs_path, weak, cx);
847 cx.background().spawn(async move {
848 let worktree = worktree.await?;
849 Ok((worktree, PathBuf::new()))
850 })
851 }
852
853 fn find_worktree_for_abs_path(
854 &self,
855 abs_path: &Path,
856 cx: &AppContext,
857 ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
858 for tree in self.worktrees(cx) {
859 if let Some(relative_path) = tree
860 .read(cx)
861 .as_local()
862 .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
863 {
864 return Some((tree.clone(), relative_path.into()));
865 }
866 }
867 None
868 }
869
870 pub fn is_shared(&self) -> bool {
871 match &self.client_state {
872 ProjectClientState::Local { is_shared, .. } => *is_shared,
873 ProjectClientState::Remote { .. } => false,
874 }
875 }
876
877 fn add_local_worktree(
878 &self,
879 abs_path: impl AsRef<Path>,
880 weak: bool,
881 cx: &mut ModelContext<Self>,
882 ) -> Task<Result<ModelHandle<Worktree>>> {
883 let fs = self.fs.clone();
884 let client = self.client.clone();
885 let user_store = self.user_store.clone();
886 let path = Arc::from(abs_path.as_ref());
887 cx.spawn(|project, mut cx| async move {
888 let worktree =
889 Worktree::open_local(client.clone(), user_store, path, weak, fs, &mut cx).await?;
890
891 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
892 project.add_worktree(&worktree, cx);
893 (project.remote_id(), project.is_shared())
894 });
895
896 if let Some(project_id) = remote_project_id {
897 worktree
898 .update(&mut cx, |worktree, cx| {
899 worktree.as_local_mut().unwrap().register(project_id, cx)
900 })
901 .await?;
902 if is_shared {
903 worktree
904 .update(&mut cx, |worktree, cx| {
905 worktree.as_local_mut().unwrap().share(cx)
906 })
907 .await?;
908 }
909 }
910
911 Ok(worktree)
912 })
913 }
914
915 pub fn remove_worktree(&mut self, id: WorktreeId, cx: &mut ModelContext<Self>) {
916 self.worktrees.retain(|worktree| {
917 worktree
918 .upgrade(cx)
919 .map_or(false, |w| w.read(cx).id() != id)
920 });
921 cx.notify();
922 }
923
924 fn add_worktree(&mut self, worktree: &ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
925 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
926
927 let push_weak_handle = {
928 let worktree = worktree.read(cx);
929 worktree.is_local() && worktree.is_weak()
930 };
931 if push_weak_handle {
932 cx.observe_release(&worktree, |this, cx| {
933 this.worktrees
934 .retain(|worktree| worktree.upgrade(cx).is_some());
935 cx.notify();
936 })
937 .detach();
938 self.worktrees
939 .push(WorktreeHandle::Weak(worktree.downgrade()));
940 } else {
941 self.worktrees
942 .push(WorktreeHandle::Strong(worktree.clone()));
943 }
944 cx.notify();
945 }
946
947 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
948 let new_active_entry = entry.and_then(|project_path| {
949 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
950 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
951 Some(ProjectEntry {
952 worktree_id: project_path.worktree_id,
953 entry_id: entry.id,
954 })
955 });
956 if new_active_entry != self.active_entry {
957 self.active_entry = new_active_entry;
958 cx.emit(Event::ActiveEntryChanged(new_active_entry));
959 }
960 }
961
962 pub fn is_running_disk_based_diagnostics(&self) -> bool {
963 self.language_servers_with_diagnostics_running > 0
964 }
965
966 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
967 let mut summary = DiagnosticSummary::default();
968 for (_, path_summary) in self.diagnostic_summaries(cx) {
969 summary.error_count += path_summary.error_count;
970 summary.warning_count += path_summary.warning_count;
971 summary.info_count += path_summary.info_count;
972 summary.hint_count += path_summary.hint_count;
973 }
974 summary
975 }
976
977 pub fn diagnostic_summaries<'a>(
978 &'a self,
979 cx: &'a AppContext,
980 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
981 self.worktrees(cx).flat_map(move |worktree| {
982 let worktree = worktree.read(cx);
983 let worktree_id = worktree.id();
984 worktree
985 .diagnostic_summaries()
986 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
987 })
988 }
989
990 fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
991 self.language_servers_with_diagnostics_running += 1;
992 if self.language_servers_with_diagnostics_running == 1 {
993 cx.emit(Event::DiskBasedDiagnosticsStarted);
994 }
995 }
996
997 fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
998 cx.emit(Event::DiskBasedDiagnosticsUpdated);
999 self.language_servers_with_diagnostics_running -= 1;
1000 if self.language_servers_with_diagnostics_running == 0 {
1001 cx.emit(Event::DiskBasedDiagnosticsFinished);
1002 }
1003 }
1004
1005 pub fn active_entry(&self) -> Option<ProjectEntry> {
1006 self.active_entry
1007 }
1008
1009 // RPC message handlers
1010
1011 fn handle_unshare_project(
1012 &mut self,
1013 _: TypedEnvelope<proto::UnshareProject>,
1014 _: Arc<Client>,
1015 cx: &mut ModelContext<Self>,
1016 ) -> Result<()> {
1017 if let ProjectClientState::Remote {
1018 sharing_has_stopped,
1019 ..
1020 } = &mut self.client_state
1021 {
1022 *sharing_has_stopped = true;
1023 self.collaborators.clear();
1024 cx.notify();
1025 Ok(())
1026 } else {
1027 unreachable!()
1028 }
1029 }
1030
1031 fn handle_add_collaborator(
1032 &mut self,
1033 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
1034 _: Arc<Client>,
1035 cx: &mut ModelContext<Self>,
1036 ) -> Result<()> {
1037 let user_store = self.user_store.clone();
1038 let collaborator = envelope
1039 .payload
1040 .collaborator
1041 .take()
1042 .ok_or_else(|| anyhow!("empty collaborator"))?;
1043
1044 cx.spawn(|this, mut cx| {
1045 async move {
1046 let collaborator =
1047 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
1048 this.update(&mut cx, |this, cx| {
1049 this.collaborators
1050 .insert(collaborator.peer_id, collaborator);
1051 cx.notify();
1052 });
1053 Ok(())
1054 }
1055 .log_err()
1056 })
1057 .detach();
1058
1059 Ok(())
1060 }
1061
1062 fn handle_remove_collaborator(
1063 &mut self,
1064 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
1065 _: Arc<Client>,
1066 cx: &mut ModelContext<Self>,
1067 ) -> Result<()> {
1068 let peer_id = PeerId(envelope.payload.peer_id);
1069 let replica_id = self
1070 .collaborators
1071 .remove(&peer_id)
1072 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
1073 .replica_id;
1074 for worktree in self.worktrees(cx).collect::<Vec<_>>() {
1075 worktree.update(cx, |worktree, cx| {
1076 worktree.remove_collaborator(peer_id, replica_id, cx);
1077 })
1078 }
1079 Ok(())
1080 }
1081
1082 fn handle_share_worktree(
1083 &mut self,
1084 envelope: TypedEnvelope<proto::ShareWorktree>,
1085 client: Arc<Client>,
1086 cx: &mut ModelContext<Self>,
1087 ) -> Result<()> {
1088 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
1089 let replica_id = self.replica_id();
1090 let worktree = envelope
1091 .payload
1092 .worktree
1093 .ok_or_else(|| anyhow!("invalid worktree"))?;
1094 let user_store = self.user_store.clone();
1095 cx.spawn(|this, mut cx| {
1096 async move {
1097 let worktree =
1098 Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx)
1099 .await?;
1100 this.update(&mut cx, |this, cx| this.add_worktree(&worktree, cx));
1101 Ok(())
1102 }
1103 .log_err()
1104 })
1105 .detach();
1106 Ok(())
1107 }
1108
1109 fn handle_unregister_worktree(
1110 &mut self,
1111 envelope: TypedEnvelope<proto::UnregisterWorktree>,
1112 _: Arc<Client>,
1113 cx: &mut ModelContext<Self>,
1114 ) -> Result<()> {
1115 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1116 self.remove_worktree(worktree_id, cx);
1117 Ok(())
1118 }
1119
1120 fn handle_update_worktree(
1121 &mut self,
1122 envelope: TypedEnvelope<proto::UpdateWorktree>,
1123 _: Arc<Client>,
1124 cx: &mut ModelContext<Self>,
1125 ) -> Result<()> {
1126 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1127 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1128 worktree.update(cx, |worktree, cx| {
1129 let worktree = worktree.as_remote_mut().unwrap();
1130 worktree.update_from_remote(envelope, cx)
1131 })?;
1132 }
1133 Ok(())
1134 }
1135
1136 fn handle_update_diagnostic_summary(
1137 &mut self,
1138 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
1139 _: Arc<Client>,
1140 cx: &mut ModelContext<Self>,
1141 ) -> Result<()> {
1142 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1143 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1144 if let Some(summary) = envelope.payload.summary {
1145 let project_path = ProjectPath {
1146 worktree_id,
1147 path: Path::new(&summary.path).into(),
1148 };
1149 worktree.update(cx, |worktree, _| {
1150 worktree
1151 .as_remote_mut()
1152 .unwrap()
1153 .update_diagnostic_summary(project_path.path.clone(), &summary);
1154 });
1155 cx.emit(Event::DiagnosticsUpdated(project_path));
1156 }
1157 }
1158 Ok(())
1159 }
1160
1161 fn handle_disk_based_diagnostics_updating(
1162 &mut self,
1163 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
1164 _: Arc<Client>,
1165 cx: &mut ModelContext<Self>,
1166 ) -> Result<()> {
1167 self.disk_based_diagnostics_started(cx);
1168 Ok(())
1169 }
1170
1171 fn handle_disk_based_diagnostics_updated(
1172 &mut self,
1173 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
1174 _: Arc<Client>,
1175 cx: &mut ModelContext<Self>,
1176 ) -> Result<()> {
1177 self.disk_based_diagnostics_finished(cx);
1178 Ok(())
1179 }
1180
1181 pub fn handle_update_buffer(
1182 &mut self,
1183 envelope: TypedEnvelope<proto::UpdateBuffer>,
1184 _: Arc<Client>,
1185 cx: &mut ModelContext<Self>,
1186 ) -> Result<()> {
1187 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1188 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1189 worktree.update(cx, |worktree, cx| {
1190 worktree.handle_update_buffer(envelope, cx)
1191 })?;
1192 }
1193 Ok(())
1194 }
1195
1196 pub fn handle_save_buffer(
1197 &mut self,
1198 envelope: TypedEnvelope<proto::SaveBuffer>,
1199 rpc: Arc<Client>,
1200 cx: &mut ModelContext<Self>,
1201 ) -> Result<()> {
1202 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1203 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1204 worktree.update(cx, |worktree, cx| {
1205 worktree.handle_save_buffer(envelope, rpc, cx)
1206 })?;
1207 }
1208 Ok(())
1209 }
1210
1211 pub fn handle_format_buffer(
1212 &mut self,
1213 envelope: TypedEnvelope<proto::FormatBuffer>,
1214 rpc: Arc<Client>,
1215 cx: &mut ModelContext<Self>,
1216 ) -> Result<()> {
1217 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1218 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1219 worktree.update(cx, |worktree, cx| {
1220 worktree.handle_format_buffer(envelope, rpc, cx)
1221 })?;
1222 }
1223 Ok(())
1224 }
1225
1226 pub fn handle_open_buffer(
1227 &mut self,
1228 envelope: TypedEnvelope<proto::OpenBuffer>,
1229 rpc: Arc<Client>,
1230 cx: &mut ModelContext<Self>,
1231 ) -> anyhow::Result<()> {
1232 let receipt = envelope.receipt();
1233 let peer_id = envelope.original_sender_id()?;
1234 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1235 let worktree = self
1236 .worktree_for_id(worktree_id, cx)
1237 .ok_or_else(|| anyhow!("no such worktree"))?;
1238
1239 let task = self.open_buffer(
1240 ProjectPath {
1241 worktree_id,
1242 path: PathBuf::from(envelope.payload.path).into(),
1243 },
1244 cx,
1245 );
1246 cx.spawn(|_, mut cx| {
1247 async move {
1248 let buffer = task.await?;
1249 let response = worktree.update(&mut cx, |worktree, cx| {
1250 worktree
1251 .as_local_mut()
1252 .unwrap()
1253 .open_remote_buffer(peer_id, buffer, cx)
1254 });
1255 rpc.respond(receipt, response).await?;
1256 Ok(())
1257 }
1258 .log_err()
1259 })
1260 .detach();
1261 Ok(())
1262 }
1263
1264 pub fn handle_close_buffer(
1265 &mut self,
1266 envelope: TypedEnvelope<proto::CloseBuffer>,
1267 _: Arc<Client>,
1268 cx: &mut ModelContext<Self>,
1269 ) -> anyhow::Result<()> {
1270 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1271 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1272 worktree.update(cx, |worktree, cx| {
1273 worktree
1274 .as_local_mut()
1275 .unwrap()
1276 .close_remote_buffer(envelope, cx)
1277 })?;
1278 }
1279 Ok(())
1280 }
1281
1282 pub fn handle_buffer_saved(
1283 &mut self,
1284 envelope: TypedEnvelope<proto::BufferSaved>,
1285 _: Arc<Client>,
1286 cx: &mut ModelContext<Self>,
1287 ) -> Result<()> {
1288 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1289 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1290 worktree.update(cx, |worktree, cx| {
1291 worktree.handle_buffer_saved(envelope, cx)
1292 })?;
1293 }
1294 Ok(())
1295 }
1296
1297 pub fn match_paths<'a>(
1298 &self,
1299 query: &'a str,
1300 include_ignored: bool,
1301 smart_case: bool,
1302 max_results: usize,
1303 cancel_flag: &'a AtomicBool,
1304 cx: &AppContext,
1305 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
1306 let worktrees = self
1307 .worktrees(cx)
1308 .filter(|worktree| !worktree.read(cx).is_weak())
1309 .collect::<Vec<_>>();
1310 let include_root_name = worktrees.len() > 1;
1311 let candidate_sets = worktrees
1312 .into_iter()
1313 .map(|worktree| CandidateSet {
1314 snapshot: worktree.read(cx).snapshot(),
1315 include_ignored,
1316 include_root_name,
1317 })
1318 .collect::<Vec<_>>();
1319
1320 let background = cx.background().clone();
1321 async move {
1322 fuzzy::match_paths(
1323 candidate_sets.as_slice(),
1324 query,
1325 smart_case,
1326 max_results,
1327 cancel_flag,
1328 background,
1329 )
1330 .await
1331 }
1332 }
1333}
1334
1335impl WorktreeHandle {
1336 pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
1337 match self {
1338 WorktreeHandle::Strong(handle) => Some(handle.clone()),
1339 WorktreeHandle::Weak(handle) => handle.upgrade(cx),
1340 }
1341 }
1342}
1343
1344struct CandidateSet {
1345 snapshot: Snapshot,
1346 include_ignored: bool,
1347 include_root_name: bool,
1348}
1349
1350impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
1351 type Candidates = CandidateSetIter<'a>;
1352
1353 fn id(&self) -> usize {
1354 self.snapshot.id().to_usize()
1355 }
1356
1357 fn len(&self) -> usize {
1358 if self.include_ignored {
1359 self.snapshot.file_count()
1360 } else {
1361 self.snapshot.visible_file_count()
1362 }
1363 }
1364
1365 fn prefix(&self) -> Arc<str> {
1366 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
1367 self.snapshot.root_name().into()
1368 } else if self.include_root_name {
1369 format!("{}/", self.snapshot.root_name()).into()
1370 } else {
1371 "".into()
1372 }
1373 }
1374
1375 fn candidates(&'a self, start: usize) -> Self::Candidates {
1376 CandidateSetIter {
1377 traversal: self.snapshot.files(self.include_ignored, start),
1378 }
1379 }
1380}
1381
1382struct CandidateSetIter<'a> {
1383 traversal: Traversal<'a>,
1384}
1385
1386impl<'a> Iterator for CandidateSetIter<'a> {
1387 type Item = PathMatchCandidate<'a>;
1388
1389 fn next(&mut self) -> Option<Self::Item> {
1390 self.traversal.next().map(|entry| {
1391 if let EntryKind::File(char_bag) = entry.kind {
1392 PathMatchCandidate {
1393 path: &entry.path,
1394 char_bag,
1395 }
1396 } else {
1397 unreachable!()
1398 }
1399 })
1400 }
1401}
1402
1403impl Entity for Project {
1404 type Event = Event;
1405
1406 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
1407 match &self.client_state {
1408 ProjectClientState::Local { remote_id_rx, .. } => {
1409 if let Some(project_id) = *remote_id_rx.borrow() {
1410 let rpc = self.client.clone();
1411 cx.spawn(|_| async move {
1412 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
1413 log::error!("error unregistering project: {}", err);
1414 }
1415 })
1416 .detach();
1417 }
1418 }
1419 ProjectClientState::Remote { remote_id, .. } => {
1420 let rpc = self.client.clone();
1421 let project_id = *remote_id;
1422 cx.spawn(|_| async move {
1423 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
1424 log::error!("error leaving project: {}", err);
1425 }
1426 })
1427 .detach();
1428 }
1429 }
1430 }
1431
1432 fn app_will_quit(
1433 &mut self,
1434 _: &mut MutableAppContext,
1435 ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
1436 use futures::FutureExt;
1437
1438 let shutdown_futures = self
1439 .language_servers
1440 .drain()
1441 .filter_map(|(_, server)| server.shutdown())
1442 .collect::<Vec<_>>();
1443 Some(
1444 async move {
1445 futures::future::join_all(shutdown_futures).await;
1446 }
1447 .boxed(),
1448 )
1449 }
1450}
1451
1452impl Collaborator {
1453 fn from_proto(
1454 message: proto::Collaborator,
1455 user_store: &ModelHandle<UserStore>,
1456 cx: &mut AsyncAppContext,
1457 ) -> impl Future<Output = Result<Self>> {
1458 let user = user_store.update(cx, |user_store, cx| {
1459 user_store.fetch_user(message.user_id, cx)
1460 });
1461
1462 async move {
1463 Ok(Self {
1464 peer_id: PeerId(message.peer_id),
1465 user: user.await?,
1466 replica_id: message.replica_id as ReplicaId,
1467 })
1468 }
1469 }
1470}
1471
1472#[cfg(test)]
1473mod tests {
1474 use super::{Event, *};
1475 use client::test::FakeHttpClient;
1476 use fs::RealFs;
1477 use futures::StreamExt;
1478 use gpui::{test::subscribe, TestAppContext};
1479 use language::{
1480 tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageRegistry,
1481 LanguageServerConfig, Point,
1482 };
1483 use lsp::Url;
1484 use serde_json::json;
1485 use std::{os::unix, path::PathBuf};
1486 use util::test::temp_tree;
1487
1488 #[gpui::test]
1489 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1490 let dir = temp_tree(json!({
1491 "root": {
1492 "apple": "",
1493 "banana": {
1494 "carrot": {
1495 "date": "",
1496 "endive": "",
1497 }
1498 },
1499 "fennel": {
1500 "grape": "",
1501 }
1502 }
1503 }));
1504
1505 let root_link_path = dir.path().join("root_link");
1506 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1507 unix::fs::symlink(
1508 &dir.path().join("root/fennel"),
1509 &dir.path().join("root/finnochio"),
1510 )
1511 .unwrap();
1512
1513 let project = build_project(&mut cx);
1514
1515 let (tree, _) = project
1516 .update(&mut cx, |project, cx| {
1517 project.find_or_create_worktree_for_abs_path(&root_link_path, false, cx)
1518 })
1519 .await
1520 .unwrap();
1521
1522 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1523 .await;
1524 cx.read(|cx| {
1525 let tree = tree.read(cx);
1526 assert_eq!(tree.file_count(), 5);
1527 assert_eq!(
1528 tree.inode_for_path("fennel/grape"),
1529 tree.inode_for_path("finnochio/grape")
1530 );
1531 });
1532
1533 let cancel_flag = Default::default();
1534 let results = project
1535 .read_with(&cx, |project, cx| {
1536 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1537 })
1538 .await;
1539 assert_eq!(
1540 results
1541 .into_iter()
1542 .map(|result| result.path)
1543 .collect::<Vec<Arc<Path>>>(),
1544 vec![
1545 PathBuf::from("banana/carrot/date").into(),
1546 PathBuf::from("banana/carrot/endive").into(),
1547 ]
1548 );
1549 }
1550
1551 #[gpui::test]
1552 async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
1553 let (language_server_config, mut fake_server) =
1554 LanguageServerConfig::fake(cx.background()).await;
1555 let progress_token = language_server_config
1556 .disk_based_diagnostics_progress_token
1557 .clone()
1558 .unwrap();
1559
1560 let mut languages = LanguageRegistry::new();
1561 languages.add(Arc::new(Language::new(
1562 LanguageConfig {
1563 name: "Rust".to_string(),
1564 path_suffixes: vec!["rs".to_string()],
1565 language_server: Some(language_server_config),
1566 ..Default::default()
1567 },
1568 Some(tree_sitter_rust::language()),
1569 )));
1570
1571 let dir = temp_tree(json!({
1572 "a.rs": "fn a() { A }",
1573 "b.rs": "const y: i32 = 1",
1574 }));
1575
1576 let http_client = FakeHttpClient::with_404_response();
1577 let client = Client::new(http_client.clone());
1578 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1579
1580 let project = cx.update(|cx| {
1581 Project::local(
1582 client,
1583 user_store,
1584 Arc::new(languages),
1585 Arc::new(RealFs),
1586 cx,
1587 )
1588 });
1589
1590 let (tree, _) = project
1591 .update(&mut cx, |project, cx| {
1592 project.find_or_create_worktree_for_abs_path(dir.path(), false, cx)
1593 })
1594 .await
1595 .unwrap();
1596 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
1597
1598 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1599 .await;
1600
1601 // Cause worktree to start the fake language server
1602 let _buffer = project
1603 .update(&mut cx, |project, cx| {
1604 project.open_buffer(
1605 ProjectPath {
1606 worktree_id,
1607 path: Path::new("b.rs").into(),
1608 },
1609 cx,
1610 )
1611 })
1612 .await
1613 .unwrap();
1614
1615 let mut events = subscribe(&project, &mut cx);
1616
1617 fake_server.start_progress(&progress_token).await;
1618 assert_eq!(
1619 events.next().await.unwrap(),
1620 Event::DiskBasedDiagnosticsStarted
1621 );
1622
1623 fake_server.start_progress(&progress_token).await;
1624 fake_server.end_progress(&progress_token).await;
1625 fake_server.start_progress(&progress_token).await;
1626
1627 fake_server
1628 .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
1629 uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
1630 version: None,
1631 diagnostics: vec![lsp::Diagnostic {
1632 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1633 severity: Some(lsp::DiagnosticSeverity::ERROR),
1634 message: "undefined variable 'A'".to_string(),
1635 ..Default::default()
1636 }],
1637 })
1638 .await;
1639 assert_eq!(
1640 events.next().await.unwrap(),
1641 Event::DiagnosticsUpdated(ProjectPath {
1642 worktree_id,
1643 path: Arc::from(Path::new("a.rs"))
1644 })
1645 );
1646
1647 fake_server.end_progress(&progress_token).await;
1648 fake_server.end_progress(&progress_token).await;
1649 assert_eq!(
1650 events.next().await.unwrap(),
1651 Event::DiskBasedDiagnosticsUpdated
1652 );
1653 assert_eq!(
1654 events.next().await.unwrap(),
1655 Event::DiskBasedDiagnosticsFinished
1656 );
1657
1658 let (buffer, _) = tree
1659 .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx))
1660 .await
1661 .unwrap();
1662
1663 buffer.read_with(&cx, |buffer, _| {
1664 let snapshot = buffer.snapshot();
1665 let diagnostics = snapshot
1666 .diagnostics_in_range::<_, Point>(0..buffer.len())
1667 .collect::<Vec<_>>();
1668 assert_eq!(
1669 diagnostics,
1670 &[DiagnosticEntry {
1671 range: Point::new(0, 9)..Point::new(0, 10),
1672 diagnostic: Diagnostic {
1673 severity: lsp::DiagnosticSeverity::ERROR,
1674 message: "undefined variable 'A'".to_string(),
1675 group_id: 0,
1676 is_primary: true,
1677 ..Default::default()
1678 }
1679 }]
1680 )
1681 });
1682 }
1683
1684 #[gpui::test]
1685 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1686 let dir = temp_tree(json!({
1687 "root": {
1688 "dir1": {},
1689 "dir2": {
1690 "dir3": {}
1691 }
1692 }
1693 }));
1694
1695 let project = build_project(&mut cx);
1696 let (tree, _) = project
1697 .update(&mut cx, |project, cx| {
1698 project.find_or_create_worktree_for_abs_path(&dir.path(), false, cx)
1699 })
1700 .await
1701 .unwrap();
1702
1703 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1704 .await;
1705
1706 let cancel_flag = Default::default();
1707 let results = project
1708 .read_with(&cx, |project, cx| {
1709 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1710 })
1711 .await;
1712
1713 assert!(results.is_empty());
1714 }
1715
1716 #[gpui::test]
1717 async fn test_definition(mut cx: gpui::TestAppContext) {
1718 let (language_server_config, mut fake_server) =
1719 LanguageServerConfig::fake(cx.background()).await;
1720
1721 let mut languages = LanguageRegistry::new();
1722 languages.add(Arc::new(Language::new(
1723 LanguageConfig {
1724 name: "Rust".to_string(),
1725 path_suffixes: vec!["rs".to_string()],
1726 language_server: Some(language_server_config),
1727 ..Default::default()
1728 },
1729 Some(tree_sitter_rust::language()),
1730 )));
1731
1732 let dir = temp_tree(json!({
1733 "a.rs": "const fn a() { A }",
1734 "b.rs": "const y: i32 = crate::a()",
1735 }));
1736
1737 let http_client = FakeHttpClient::with_404_response();
1738 let client = Client::new(http_client.clone());
1739 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1740 let project = cx.update(|cx| {
1741 Project::local(
1742 client,
1743 user_store,
1744 Arc::new(languages),
1745 Arc::new(RealFs),
1746 cx,
1747 )
1748 });
1749
1750 let (tree, _) = project
1751 .update(&mut cx, |project, cx| {
1752 project.find_or_create_worktree_for_abs_path(dir.path().join("b.rs"), false, cx)
1753 })
1754 .await
1755 .unwrap();
1756 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
1757 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1758 .await;
1759
1760 // Cause worktree to start the fake language server
1761 let buffer = project
1762 .update(&mut cx, |project, cx| {
1763 project.open_buffer(
1764 ProjectPath {
1765 worktree_id,
1766 path: Path::new("").into(),
1767 },
1768 cx,
1769 )
1770 })
1771 .await
1772 .unwrap();
1773 let definitions =
1774 project.update(&mut cx, |project, cx| project.definition(&buffer, 22, cx));
1775 let (request_id, request) = fake_server
1776 .receive_request::<lsp::request::GotoDefinition>()
1777 .await;
1778 let request_params = request.text_document_position_params;
1779 assert_eq!(
1780 request_params.text_document.uri.to_file_path().unwrap(),
1781 dir.path().join("b.rs")
1782 );
1783 assert_eq!(request_params.position, lsp::Position::new(0, 22));
1784
1785 fake_server
1786 .respond(
1787 request_id,
1788 Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
1789 lsp::Url::from_file_path(dir.path().join("a.rs")).unwrap(),
1790 lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1791 ))),
1792 )
1793 .await;
1794 let mut definitions = definitions.await.unwrap();
1795 assert_eq!(definitions.len(), 1);
1796 let definition = definitions.pop().unwrap();
1797 cx.update(|cx| {
1798 let target_buffer = definition.target_buffer.read(cx);
1799 assert_eq!(
1800 target_buffer.file().unwrap().abs_path(),
1801 Some(dir.path().join("a.rs"))
1802 );
1803 assert_eq!(definition.target_range.to_offset(target_buffer), 9..10);
1804 assert_eq!(
1805 list_worktrees(&project, cx),
1806 [
1807 (dir.path().join("b.rs"), false),
1808 (dir.path().join("a.rs"), true)
1809 ]
1810 );
1811
1812 drop(definition);
1813 });
1814 cx.read(|cx| {
1815 assert_eq!(
1816 list_worktrees(&project, cx),
1817 [(dir.path().join("b.rs"), false)]
1818 );
1819 });
1820
1821 fn list_worktrees(project: &ModelHandle<Project>, cx: &AppContext) -> Vec<(PathBuf, bool)> {
1822 project
1823 .read(cx)
1824 .worktrees(cx)
1825 .map(|worktree| {
1826 let worktree = worktree.read(cx);
1827 (
1828 worktree.as_local().unwrap().abs_path().to_path_buf(),
1829 worktree.is_weak(),
1830 )
1831 })
1832 .collect::<Vec<_>>()
1833 }
1834 }
1835
1836 fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1837 let languages = Arc::new(LanguageRegistry::new());
1838 let fs = Arc::new(RealFs);
1839 let http_client = FakeHttpClient::with_404_response();
1840 let client = client::Client::new(http_client.clone());
1841 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1842 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1843 }
1844}