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