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 let (worktree, relative_path) = self
680 .find_worktree_for_abs_path(&path, cx)
681 .ok_or_else(|| anyhow!("no worktree found for diagnostics"))?;
682 let project_path = ProjectPath {
683 worktree_id: worktree.read(cx).id(),
684 path: relative_path.into(),
685 };
686 worktree.update(cx, |worktree, cx| {
687 worktree.as_local_mut().unwrap().update_diagnostics(
688 project_path.path.clone(),
689 diagnostics,
690 disk_based_sources,
691 cx,
692 )
693 })?;
694 cx.emit(Event::DiagnosticsUpdated(project_path));
695 Ok(())
696 }
697
698 pub fn definition<T: ToOffset>(
699 &self,
700 source_buffer_handle: &ModelHandle<Buffer>,
701 position: T,
702 cx: &mut ModelContext<Self>,
703 ) -> Task<Result<Vec<Definition>>> {
704 let source_buffer_handle = source_buffer_handle.clone();
705 let buffer = source_buffer_handle.read(cx);
706 let worktree;
707 let buffer_abs_path;
708 if let Some(file) = File::from_dyn(buffer.file()) {
709 worktree = file.worktree.clone();
710 buffer_abs_path = file.abs_path();
711 } else {
712 return Task::ready(Err(anyhow!("buffer does not belong to any worktree")));
713 };
714
715 if worktree.read(cx).as_local().is_some() {
716 let point = buffer.offset_to_point_utf16(position.to_offset(buffer));
717 let buffer_abs_path = buffer_abs_path.unwrap();
718 let lang_name;
719 let lang_server;
720 if let Some(lang) = buffer.language() {
721 lang_name = lang.name().to_string();
722 if let Some(server) = self
723 .language_servers
724 .get(&(worktree.read(cx).id(), lang_name.clone()))
725 {
726 lang_server = server.clone();
727 } else {
728 return Task::ready(Err(anyhow!("buffer does not have a language server")));
729 };
730 } else {
731 return Task::ready(Err(anyhow!("buffer does not have a language")));
732 }
733
734 cx.spawn(|this, mut cx| async move {
735 let response = lang_server
736 .request::<lsp::request::GotoDefinition>(lsp::GotoDefinitionParams {
737 text_document_position_params: lsp::TextDocumentPositionParams {
738 text_document: lsp::TextDocumentIdentifier::new(
739 lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
740 ),
741 position: lsp::Position::new(point.row, point.column),
742 },
743 work_done_progress_params: Default::default(),
744 partial_result_params: Default::default(),
745 })
746 .await?;
747
748 let mut definitions = Vec::new();
749 if let Some(response) = response {
750 let mut unresolved_locations = Vec::new();
751 match response {
752 lsp::GotoDefinitionResponse::Scalar(loc) => {
753 unresolved_locations.push((None, loc.uri, loc.range));
754 }
755 lsp::GotoDefinitionResponse::Array(locs) => {
756 unresolved_locations
757 .extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
758 }
759 lsp::GotoDefinitionResponse::Link(links) => {
760 unresolved_locations.extend(links.into_iter().map(|l| {
761 (
762 l.origin_selection_range,
763 l.target_uri,
764 l.target_selection_range,
765 )
766 }));
767 }
768 }
769
770 for (source_range, target_uri, target_range) in unresolved_locations {
771 let abs_path = target_uri
772 .to_file_path()
773 .map_err(|_| anyhow!("invalid target path"))?;
774
775 let (worktree, relative_path) = if let Some(result) = this
776 .read_with(&cx, |this, cx| {
777 this.find_worktree_for_abs_path(&abs_path, cx)
778 }) {
779 result
780 } else {
781 let (worktree, relative_path) = this
782 .update(&mut cx, |this, cx| {
783 this.create_worktree_for_abs_path(&abs_path, cx)
784 })
785 .await?;
786 this.update(&mut cx, |this, cx| {
787 this.language_servers.insert(
788 (worktree.read(cx).id(), lang_name.clone()),
789 lang_server.clone(),
790 );
791 });
792 (worktree, relative_path)
793 };
794
795 let project_path = ProjectPath {
796 worktree_id: worktree.read_with(&cx, |worktree, _| worktree.id()),
797 path: relative_path.into(),
798 };
799 let target_buffer_handle = this
800 .update(&mut cx, |this, cx| this.open_buffer(project_path, cx))
801 .await?;
802 cx.read(|cx| {
803 let source_buffer = source_buffer_handle.read(cx);
804 let target_buffer = target_buffer_handle.read(cx);
805 let source_range = source_range.map(|range| {
806 let start = source_buffer
807 .clip_point_utf16(range.start.to_point_utf16(), Bias::Left);
808 let end = source_buffer
809 .clip_point_utf16(range.end.to_point_utf16(), Bias::Left);
810 source_buffer.anchor_after(start)..source_buffer.anchor_before(end)
811 });
812 let target_start = target_buffer
813 .clip_point_utf16(target_range.start.to_point_utf16(), Bias::Left);
814 let target_end = target_buffer
815 .clip_point_utf16(target_range.end.to_point_utf16(), Bias::Left);
816 definitions.push(Definition {
817 source_range,
818 target_buffer: target_buffer_handle,
819 target_range: target_buffer.anchor_after(target_start)
820 ..target_buffer.anchor_before(target_end),
821 });
822 });
823 }
824 }
825
826 Ok(definitions)
827 })
828 } else {
829 todo!()
830 }
831 }
832
833 pub fn find_or_create_worktree_for_abs_path(
834 &self,
835 abs_path: &Path,
836 cx: &mut ModelContext<Self>,
837 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
838 if let Some((tree, relative_path)) = self.find_worktree_for_abs_path(abs_path, cx) {
839 Task::ready(Ok((tree.clone(), relative_path.into())))
840 } else {
841 self.create_worktree_for_abs_path(abs_path, cx)
842 }
843 }
844
845 fn create_worktree_for_abs_path(
846 &self,
847 abs_path: &Path,
848 cx: &mut ModelContext<Self>,
849 ) -> Task<Result<(ModelHandle<Worktree>, PathBuf)>> {
850 let worktree = self.add_local_worktree(abs_path, cx);
851 cx.background().spawn(async move {
852 let worktree = worktree.await?;
853 Ok((worktree, PathBuf::new()))
854 })
855 }
856
857 fn find_worktree_for_abs_path(
858 &self,
859 abs_path: &Path,
860 cx: &AppContext,
861 ) -> Option<(ModelHandle<Worktree>, PathBuf)> {
862 for tree in &self.worktrees {
863 if let Some(relative_path) = tree
864 .read(cx)
865 .as_local()
866 .and_then(|t| abs_path.strip_prefix(t.abs_path()).ok())
867 {
868 return Some((tree.clone(), relative_path.into()));
869 }
870 }
871 None
872 }
873
874 pub fn is_shared(&self) -> bool {
875 match &self.client_state {
876 ProjectClientState::Local { is_shared, .. } => *is_shared,
877 ProjectClientState::Remote { .. } => false,
878 }
879 }
880
881 pub fn add_local_worktree(
882 &self,
883 abs_path: impl AsRef<Path>,
884 cx: &mut ModelContext<Self>,
885 ) -> Task<Result<ModelHandle<Worktree>>> {
886 let fs = self.fs.clone();
887 let client = self.client.clone();
888 let user_store = self.user_store.clone();
889 let path = Arc::from(abs_path.as_ref());
890 cx.spawn(|project, mut cx| async move {
891 let worktree =
892 Worktree::open_local(client.clone(), user_store, path, fs, &mut cx).await?;
893
894 let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
895 project.add_worktree(worktree.clone(), cx);
896 (project.remote_id(), project.is_shared())
897 });
898
899 if let Some(project_id) = remote_project_id {
900 let worktree_id = worktree.id() as u64;
901 let register_message = worktree.update(&mut cx, |worktree, _| {
902 let worktree = worktree.as_local_mut().unwrap();
903 proto::RegisterWorktree {
904 project_id,
905 worktree_id,
906 root_name: worktree.root_name().to_string(),
907 authorized_logins: worktree.authorized_logins(),
908 }
909 });
910 client.request(register_message).await?;
911 if is_shared {
912 worktree
913 .update(&mut cx, |worktree, cx| {
914 worktree.as_local_mut().unwrap().share(project_id, cx)
915 })
916 .await?;
917 }
918 }
919
920 Ok(worktree)
921 })
922 }
923
924 fn add_worktree(&mut self, worktree: ModelHandle<Worktree>, cx: &mut ModelContext<Self>) {
925 cx.observe(&worktree, |_, _, cx| cx.notify()).detach();
926 self.worktrees.push(worktree);
927 cx.notify();
928 }
929
930 pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
931 let new_active_entry = entry.and_then(|project_path| {
932 let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
933 let entry = worktree.read(cx).entry_for_path(project_path.path)?;
934 Some(ProjectEntry {
935 worktree_id: project_path.worktree_id,
936 entry_id: entry.id,
937 })
938 });
939 if new_active_entry != self.active_entry {
940 self.active_entry = new_active_entry;
941 cx.emit(Event::ActiveEntryChanged(new_active_entry));
942 }
943 }
944
945 pub fn path_for_entry(&self, entry: ProjectEntry, cx: &AppContext) -> Option<ProjectPath> {
946 let worktree = self.worktree_for_id(entry.worktree_id, cx)?.read(cx);
947 Some(ProjectPath {
948 worktree_id: entry.worktree_id,
949 path: worktree.entry_for_id(entry.entry_id)?.path.clone(),
950 })
951 }
952
953 pub fn is_running_disk_based_diagnostics(&self) -> bool {
954 self.language_servers_with_diagnostics_running > 0
955 }
956
957 pub fn diagnostic_summary(&self, cx: &AppContext) -> DiagnosticSummary {
958 let mut summary = DiagnosticSummary::default();
959 for (_, path_summary) in self.diagnostic_summaries(cx) {
960 summary.error_count += path_summary.error_count;
961 summary.warning_count += path_summary.warning_count;
962 summary.info_count += path_summary.info_count;
963 summary.hint_count += path_summary.hint_count;
964 }
965 summary
966 }
967
968 pub fn diagnostic_summaries<'a>(
969 &'a self,
970 cx: &'a AppContext,
971 ) -> impl Iterator<Item = (ProjectPath, DiagnosticSummary)> + 'a {
972 self.worktrees.iter().flat_map(move |worktree| {
973 let worktree = worktree.read(cx);
974 let worktree_id = worktree.id();
975 worktree
976 .diagnostic_summaries()
977 .map(move |(path, summary)| (ProjectPath { worktree_id, path }, summary))
978 })
979 }
980
981 fn disk_based_diagnostics_started(&mut self, cx: &mut ModelContext<Self>) {
982 self.language_servers_with_diagnostics_running += 1;
983 if self.language_servers_with_diagnostics_running == 1 {
984 cx.emit(Event::DiskBasedDiagnosticsStarted);
985 }
986 }
987
988 fn disk_based_diagnostics_finished(&mut self, cx: &mut ModelContext<Self>) {
989 cx.emit(Event::DiskBasedDiagnosticsUpdated);
990 self.language_servers_with_diagnostics_running -= 1;
991 if self.language_servers_with_diagnostics_running == 0 {
992 cx.emit(Event::DiskBasedDiagnosticsFinished);
993 }
994 }
995
996 pub fn active_entry(&self) -> Option<ProjectEntry> {
997 self.active_entry
998 }
999
1000 // RPC message handlers
1001
1002 fn handle_unshare_project(
1003 &mut self,
1004 _: TypedEnvelope<proto::UnshareProject>,
1005 _: Arc<Client>,
1006 cx: &mut ModelContext<Self>,
1007 ) -> Result<()> {
1008 if let ProjectClientState::Remote {
1009 sharing_has_stopped,
1010 ..
1011 } = &mut self.client_state
1012 {
1013 *sharing_has_stopped = true;
1014 self.collaborators.clear();
1015 cx.notify();
1016 Ok(())
1017 } else {
1018 unreachable!()
1019 }
1020 }
1021
1022 fn handle_add_collaborator(
1023 &mut self,
1024 mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
1025 _: Arc<Client>,
1026 cx: &mut ModelContext<Self>,
1027 ) -> Result<()> {
1028 let user_store = self.user_store.clone();
1029 let collaborator = envelope
1030 .payload
1031 .collaborator
1032 .take()
1033 .ok_or_else(|| anyhow!("empty collaborator"))?;
1034
1035 cx.spawn(|this, mut cx| {
1036 async move {
1037 let collaborator =
1038 Collaborator::from_proto(collaborator, &user_store, &mut cx).await?;
1039 this.update(&mut cx, |this, cx| {
1040 this.collaborators
1041 .insert(collaborator.peer_id, collaborator);
1042 cx.notify();
1043 });
1044 Ok(())
1045 }
1046 .log_err()
1047 })
1048 .detach();
1049
1050 Ok(())
1051 }
1052
1053 fn handle_remove_collaborator(
1054 &mut self,
1055 envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
1056 _: Arc<Client>,
1057 cx: &mut ModelContext<Self>,
1058 ) -> Result<()> {
1059 let peer_id = PeerId(envelope.payload.peer_id);
1060 let replica_id = self
1061 .collaborators
1062 .remove(&peer_id)
1063 .ok_or_else(|| anyhow!("unknown peer {:?}", peer_id))?
1064 .replica_id;
1065 for worktree in &self.worktrees {
1066 worktree.update(cx, |worktree, cx| {
1067 worktree.remove_collaborator(peer_id, replica_id, cx);
1068 })
1069 }
1070 Ok(())
1071 }
1072
1073 fn handle_share_worktree(
1074 &mut self,
1075 envelope: TypedEnvelope<proto::ShareWorktree>,
1076 client: Arc<Client>,
1077 cx: &mut ModelContext<Self>,
1078 ) -> Result<()> {
1079 let remote_id = self.remote_id().ok_or_else(|| anyhow!("invalid project"))?;
1080 let replica_id = self.replica_id();
1081 let worktree = envelope
1082 .payload
1083 .worktree
1084 .ok_or_else(|| anyhow!("invalid worktree"))?;
1085 let user_store = self.user_store.clone();
1086 cx.spawn(|this, mut cx| {
1087 async move {
1088 let worktree =
1089 Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx)
1090 .await?;
1091 this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx));
1092 Ok(())
1093 }
1094 .log_err()
1095 })
1096 .detach();
1097 Ok(())
1098 }
1099
1100 fn handle_unregister_worktree(
1101 &mut self,
1102 envelope: TypedEnvelope<proto::UnregisterWorktree>,
1103 _: Arc<Client>,
1104 cx: &mut ModelContext<Self>,
1105 ) -> Result<()> {
1106 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1107 self.worktrees
1108 .retain(|worktree| worktree.read(cx).as_remote().unwrap().id() != worktree_id);
1109 cx.notify();
1110 Ok(())
1111 }
1112
1113 fn handle_update_worktree(
1114 &mut self,
1115 envelope: TypedEnvelope<proto::UpdateWorktree>,
1116 _: Arc<Client>,
1117 cx: &mut ModelContext<Self>,
1118 ) -> Result<()> {
1119 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1120 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1121 worktree.update(cx, |worktree, cx| {
1122 let worktree = worktree.as_remote_mut().unwrap();
1123 worktree.update_from_remote(envelope, cx)
1124 })?;
1125 }
1126 Ok(())
1127 }
1128
1129 fn handle_update_diagnostic_summary(
1130 &mut self,
1131 envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
1132 _: Arc<Client>,
1133 cx: &mut ModelContext<Self>,
1134 ) -> Result<()> {
1135 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1136 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1137 if let Some(summary) = envelope.payload.summary {
1138 let project_path = ProjectPath {
1139 worktree_id,
1140 path: Path::new(&summary.path).into(),
1141 };
1142 worktree.update(cx, |worktree, _| {
1143 worktree
1144 .as_remote_mut()
1145 .unwrap()
1146 .update_diagnostic_summary(project_path.path.clone(), &summary);
1147 });
1148 cx.emit(Event::DiagnosticsUpdated(project_path));
1149 }
1150 }
1151 Ok(())
1152 }
1153
1154 fn handle_disk_based_diagnostics_updating(
1155 &mut self,
1156 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
1157 _: Arc<Client>,
1158 cx: &mut ModelContext<Self>,
1159 ) -> Result<()> {
1160 self.disk_based_diagnostics_started(cx);
1161 Ok(())
1162 }
1163
1164 fn handle_disk_based_diagnostics_updated(
1165 &mut self,
1166 _: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
1167 _: Arc<Client>,
1168 cx: &mut ModelContext<Self>,
1169 ) -> Result<()> {
1170 self.disk_based_diagnostics_finished(cx);
1171 Ok(())
1172 }
1173
1174 pub fn handle_update_buffer(
1175 &mut self,
1176 envelope: TypedEnvelope<proto::UpdateBuffer>,
1177 _: Arc<Client>,
1178 cx: &mut ModelContext<Self>,
1179 ) -> Result<()> {
1180 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1181 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1182 worktree.update(cx, |worktree, cx| {
1183 worktree.handle_update_buffer(envelope, cx)
1184 })?;
1185 }
1186 Ok(())
1187 }
1188
1189 pub fn handle_save_buffer(
1190 &mut self,
1191 envelope: TypedEnvelope<proto::SaveBuffer>,
1192 rpc: Arc<Client>,
1193 cx: &mut ModelContext<Self>,
1194 ) -> Result<()> {
1195 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1196 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1197 worktree.update(cx, |worktree, cx| {
1198 worktree.handle_save_buffer(envelope, rpc, cx)
1199 })?;
1200 }
1201 Ok(())
1202 }
1203
1204 pub fn handle_format_buffer(
1205 &mut self,
1206 envelope: TypedEnvelope<proto::FormatBuffer>,
1207 rpc: Arc<Client>,
1208 cx: &mut ModelContext<Self>,
1209 ) -> Result<()> {
1210 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1211 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1212 worktree.update(cx, |worktree, cx| {
1213 worktree.handle_format_buffer(envelope, rpc, cx)
1214 })?;
1215 }
1216 Ok(())
1217 }
1218
1219 pub fn handle_open_buffer(
1220 &mut self,
1221 envelope: TypedEnvelope<proto::OpenBuffer>,
1222 rpc: Arc<Client>,
1223 cx: &mut ModelContext<Self>,
1224 ) -> anyhow::Result<()> {
1225 let receipt = envelope.receipt();
1226 let peer_id = envelope.original_sender_id()?;
1227 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1228 let worktree = self
1229 .worktree_for_id(worktree_id, cx)
1230 .ok_or_else(|| anyhow!("no such worktree"))?;
1231
1232 let task = self.open_buffer(
1233 ProjectPath {
1234 worktree_id,
1235 path: PathBuf::from(envelope.payload.path).into(),
1236 },
1237 cx,
1238 );
1239 cx.spawn(|_, mut cx| {
1240 async move {
1241 let buffer = task.await?;
1242 let response = worktree.update(&mut cx, |worktree, cx| {
1243 worktree
1244 .as_local_mut()
1245 .unwrap()
1246 .open_remote_buffer(peer_id, buffer, cx)
1247 });
1248 rpc.respond(receipt, response).await?;
1249 Ok(())
1250 }
1251 .log_err()
1252 })
1253 .detach();
1254 Ok(())
1255 }
1256
1257 pub fn handle_close_buffer(
1258 &mut self,
1259 envelope: TypedEnvelope<proto::CloseBuffer>,
1260 _: Arc<Client>,
1261 cx: &mut ModelContext<Self>,
1262 ) -> anyhow::Result<()> {
1263 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1264 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1265 worktree.update(cx, |worktree, cx| {
1266 worktree
1267 .as_local_mut()
1268 .unwrap()
1269 .close_remote_buffer(envelope, cx)
1270 })?;
1271 }
1272 Ok(())
1273 }
1274
1275 pub fn handle_buffer_saved(
1276 &mut self,
1277 envelope: TypedEnvelope<proto::BufferSaved>,
1278 _: Arc<Client>,
1279 cx: &mut ModelContext<Self>,
1280 ) -> Result<()> {
1281 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1282 if let Some(worktree) = self.worktree_for_id(worktree_id, cx) {
1283 worktree.update(cx, |worktree, cx| {
1284 worktree.handle_buffer_saved(envelope, cx)
1285 })?;
1286 }
1287 Ok(())
1288 }
1289
1290 pub fn match_paths<'a>(
1291 &self,
1292 query: &'a str,
1293 include_ignored: bool,
1294 smart_case: bool,
1295 max_results: usize,
1296 cancel_flag: &'a AtomicBool,
1297 cx: &AppContext,
1298 ) -> impl 'a + Future<Output = Vec<PathMatch>> {
1299 let include_root_name = self.worktrees.len() > 1;
1300 let candidate_sets = self
1301 .worktrees
1302 .iter()
1303 .map(|worktree| CandidateSet {
1304 snapshot: worktree.read(cx).snapshot(),
1305 include_ignored,
1306 include_root_name,
1307 })
1308 .collect::<Vec<_>>();
1309
1310 let background = cx.background().clone();
1311 async move {
1312 fuzzy::match_paths(
1313 candidate_sets.as_slice(),
1314 query,
1315 smart_case,
1316 max_results,
1317 cancel_flag,
1318 background,
1319 )
1320 .await
1321 }
1322 }
1323}
1324
1325struct CandidateSet {
1326 snapshot: Snapshot,
1327 include_ignored: bool,
1328 include_root_name: bool,
1329}
1330
1331impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
1332 type Candidates = CandidateSetIter<'a>;
1333
1334 fn id(&self) -> usize {
1335 self.snapshot.id().to_usize()
1336 }
1337
1338 fn len(&self) -> usize {
1339 if self.include_ignored {
1340 self.snapshot.file_count()
1341 } else {
1342 self.snapshot.visible_file_count()
1343 }
1344 }
1345
1346 fn prefix(&self) -> Arc<str> {
1347 if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
1348 self.snapshot.root_name().into()
1349 } else if self.include_root_name {
1350 format!("{}/", self.snapshot.root_name()).into()
1351 } else {
1352 "".into()
1353 }
1354 }
1355
1356 fn candidates(&'a self, start: usize) -> Self::Candidates {
1357 CandidateSetIter {
1358 traversal: self.snapshot.files(self.include_ignored, start),
1359 }
1360 }
1361}
1362
1363struct CandidateSetIter<'a> {
1364 traversal: Traversal<'a>,
1365}
1366
1367impl<'a> Iterator for CandidateSetIter<'a> {
1368 type Item = PathMatchCandidate<'a>;
1369
1370 fn next(&mut self) -> Option<Self::Item> {
1371 self.traversal.next().map(|entry| {
1372 if let EntryKind::File(char_bag) = entry.kind {
1373 PathMatchCandidate {
1374 path: &entry.path,
1375 char_bag,
1376 }
1377 } else {
1378 unreachable!()
1379 }
1380 })
1381 }
1382}
1383
1384impl Entity for Project {
1385 type Event = Event;
1386
1387 fn release(&mut self, cx: &mut gpui::MutableAppContext) {
1388 match &self.client_state {
1389 ProjectClientState::Local { remote_id_rx, .. } => {
1390 if let Some(project_id) = *remote_id_rx.borrow() {
1391 let rpc = self.client.clone();
1392 cx.spawn(|_| async move {
1393 if let Err(err) = rpc.send(proto::UnregisterProject { project_id }).await {
1394 log::error!("error unregistering project: {}", err);
1395 }
1396 })
1397 .detach();
1398 }
1399 }
1400 ProjectClientState::Remote { remote_id, .. } => {
1401 let rpc = self.client.clone();
1402 let project_id = *remote_id;
1403 cx.spawn(|_| async move {
1404 if let Err(err) = rpc.send(proto::LeaveProject { project_id }).await {
1405 log::error!("error leaving project: {}", err);
1406 }
1407 })
1408 .detach();
1409 }
1410 }
1411 }
1412
1413 fn app_will_quit(
1414 &mut self,
1415 _: &mut MutableAppContext,
1416 ) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
1417 use futures::FutureExt;
1418
1419 let shutdown_futures = self
1420 .language_servers
1421 .drain()
1422 .filter_map(|(_, server)| server.shutdown())
1423 .collect::<Vec<_>>();
1424 Some(
1425 async move {
1426 futures::future::join_all(shutdown_futures).await;
1427 }
1428 .boxed(),
1429 )
1430 }
1431}
1432
1433impl Collaborator {
1434 fn from_proto(
1435 message: proto::Collaborator,
1436 user_store: &ModelHandle<UserStore>,
1437 cx: &mut AsyncAppContext,
1438 ) -> impl Future<Output = Result<Self>> {
1439 let user = user_store.update(cx, |user_store, cx| {
1440 user_store.fetch_user(message.user_id, cx)
1441 });
1442
1443 async move {
1444 Ok(Self {
1445 peer_id: PeerId(message.peer_id),
1446 user: user.await?,
1447 replica_id: message.replica_id as ReplicaId,
1448 })
1449 }
1450 }
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455 use super::{Event, *};
1456 use client::test::FakeHttpClient;
1457 use fs::RealFs;
1458 use futures::StreamExt;
1459 use gpui::{test::subscribe, TestAppContext};
1460 use language::{
1461 tree_sitter_rust, Diagnostic, LanguageConfig, LanguageRegistry, LanguageServerConfig, Point,
1462 };
1463 use lsp::Url;
1464 use serde_json::json;
1465 use std::{os::unix, path::PathBuf};
1466 use util::test::temp_tree;
1467
1468 #[gpui::test]
1469 async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
1470 let dir = temp_tree(json!({
1471 "root": {
1472 "apple": "",
1473 "banana": {
1474 "carrot": {
1475 "date": "",
1476 "endive": "",
1477 }
1478 },
1479 "fennel": {
1480 "grape": "",
1481 }
1482 }
1483 }));
1484
1485 let root_link_path = dir.path().join("root_link");
1486 unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
1487 unix::fs::symlink(
1488 &dir.path().join("root/fennel"),
1489 &dir.path().join("root/finnochio"),
1490 )
1491 .unwrap();
1492
1493 let project = build_project(&mut cx);
1494
1495 let tree = project
1496 .update(&mut cx, |project, cx| {
1497 project.add_local_worktree(&root_link_path, cx)
1498 })
1499 .await
1500 .unwrap();
1501
1502 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1503 .await;
1504 cx.read(|cx| {
1505 let tree = tree.read(cx);
1506 assert_eq!(tree.file_count(), 5);
1507 assert_eq!(
1508 tree.inode_for_path("fennel/grape"),
1509 tree.inode_for_path("finnochio/grape")
1510 );
1511 });
1512
1513 let cancel_flag = Default::default();
1514 let results = project
1515 .read_with(&cx, |project, cx| {
1516 project.match_paths("bna", false, false, 10, &cancel_flag, cx)
1517 })
1518 .await;
1519 assert_eq!(
1520 results
1521 .into_iter()
1522 .map(|result| result.path)
1523 .collect::<Vec<Arc<Path>>>(),
1524 vec![
1525 PathBuf::from("banana/carrot/date").into(),
1526 PathBuf::from("banana/carrot/endive").into(),
1527 ]
1528 );
1529 }
1530
1531 #[gpui::test]
1532 async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
1533 let (language_server_config, mut fake_server) =
1534 LanguageServerConfig::fake(cx.background()).await;
1535 let progress_token = language_server_config
1536 .disk_based_diagnostics_progress_token
1537 .clone()
1538 .unwrap();
1539
1540 let mut languages = LanguageRegistry::new();
1541 languages.add(Arc::new(Language::new(
1542 LanguageConfig {
1543 name: "Rust".to_string(),
1544 path_suffixes: vec!["rs".to_string()],
1545 language_server: Some(language_server_config),
1546 ..Default::default()
1547 },
1548 Some(tree_sitter_rust::language()),
1549 )));
1550
1551 let dir = temp_tree(json!({
1552 "a.rs": "fn a() { A }",
1553 "b.rs": "const y: i32 = 1",
1554 }));
1555
1556 let http_client = FakeHttpClient::with_404_response();
1557 let client = Client::new(http_client.clone());
1558 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1559
1560 let project = cx.update(|cx| {
1561 Project::local(
1562 client,
1563 user_store,
1564 Arc::new(languages),
1565 Arc::new(RealFs),
1566 cx,
1567 )
1568 });
1569
1570 let tree = project
1571 .update(&mut cx, |project, cx| {
1572 project.add_local_worktree(dir.path(), cx)
1573 })
1574 .await
1575 .unwrap();
1576 let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
1577
1578 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1579 .await;
1580
1581 // Cause worktree to start the fake language server
1582 let _buffer = project
1583 .update(&mut cx, |project, cx| {
1584 project.open_buffer(
1585 ProjectPath {
1586 worktree_id,
1587 path: Path::new("b.rs").into(),
1588 },
1589 cx,
1590 )
1591 })
1592 .await
1593 .unwrap();
1594
1595 let mut events = subscribe(&project, &mut cx);
1596
1597 fake_server.start_progress(&progress_token).await;
1598 assert_eq!(
1599 events.next().await.unwrap(),
1600 Event::DiskBasedDiagnosticsStarted
1601 );
1602
1603 fake_server.start_progress(&progress_token).await;
1604 fake_server.end_progress(&progress_token).await;
1605 fake_server.start_progress(&progress_token).await;
1606
1607 fake_server
1608 .notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
1609 uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
1610 version: None,
1611 diagnostics: vec![lsp::Diagnostic {
1612 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
1613 severity: Some(lsp::DiagnosticSeverity::ERROR),
1614 message: "undefined variable 'A'".to_string(),
1615 ..Default::default()
1616 }],
1617 })
1618 .await;
1619 assert_eq!(
1620 events.next().await.unwrap(),
1621 Event::DiagnosticsUpdated(ProjectPath {
1622 worktree_id,
1623 path: Arc::from(Path::new("a.rs"))
1624 })
1625 );
1626
1627 fake_server.end_progress(&progress_token).await;
1628 fake_server.end_progress(&progress_token).await;
1629 assert_eq!(
1630 events.next().await.unwrap(),
1631 Event::DiskBasedDiagnosticsUpdated
1632 );
1633 assert_eq!(
1634 events.next().await.unwrap(),
1635 Event::DiskBasedDiagnosticsFinished
1636 );
1637
1638 let (buffer, _) = tree
1639 .update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx))
1640 .await
1641 .unwrap();
1642
1643 buffer.read_with(&cx, |buffer, _| {
1644 let snapshot = buffer.snapshot();
1645 let diagnostics = snapshot
1646 .diagnostics_in_range::<_, Point>(0..buffer.len())
1647 .collect::<Vec<_>>();
1648 assert_eq!(
1649 diagnostics,
1650 &[DiagnosticEntry {
1651 range: Point::new(0, 9)..Point::new(0, 10),
1652 diagnostic: Diagnostic {
1653 severity: lsp::DiagnosticSeverity::ERROR,
1654 message: "undefined variable 'A'".to_string(),
1655 group_id: 0,
1656 is_primary: true,
1657 ..Default::default()
1658 }
1659 }]
1660 )
1661 });
1662 }
1663
1664 #[gpui::test]
1665 async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
1666 let dir = temp_tree(json!({
1667 "root": {
1668 "dir1": {},
1669 "dir2": {
1670 "dir3": {}
1671 }
1672 }
1673 }));
1674
1675 let project = build_project(&mut cx);
1676 let tree = project
1677 .update(&mut cx, |project, cx| {
1678 project.add_local_worktree(&dir.path(), cx)
1679 })
1680 .await
1681 .unwrap();
1682
1683 cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
1684 .await;
1685
1686 let cancel_flag = Default::default();
1687 let results = project
1688 .read_with(&cx, |project, cx| {
1689 project.match_paths("dir", false, false, 10, &cancel_flag, cx)
1690 })
1691 .await;
1692
1693 assert!(results.is_empty());
1694 }
1695
1696 fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
1697 let languages = Arc::new(LanguageRegistry::new());
1698 let fs = Arc::new(RealFs);
1699 let http_client = FakeHttpClient::with_404_response();
1700 let client = client::Client::new(http_client.clone());
1701 let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
1702 cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
1703 }
1704}